Test Setup Failed
Push — master ( 26dc88...d51274 )
by Tomasz
05:33 queued 11s
created

UserAPI::generateInstaller()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 26
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 20
c 0
b 0
f 0
dl 0
loc 26
rs 8.9777
cc 6
nc 4
nop 5
1
<?php
2
3
/*
4
 * *****************************************************************************
5
 * Contributions to this work were made on behalf of the GÉANT project, a 
6
 * project that has received funding from the European Union’s Framework 
7
 * Programme 7 under Grant Agreements No. 238875 (GN3) and No. 605243 (GN3plus),
8
 * Horizon 2020 research and innovation programme under Grant Agreements No. 
9
 * 691567 (GN4-1) and No. 731122 (GN4-2).
10
 * On behalf of the aforementioned projects, GEANT Association is the sole owner
11
 * of the copyright in all material which was developed by a member of the GÉANT
12
 * project. GÉANT Vereniging (Association) is registered with the Chamber of 
13
 * Commerce in Amsterdam with registration number 40535155 and operates in the 
14
 * UK as a branch of GÉANT Vereniging.
15
 * 
16
 * Registered office: Hoekenrode 3, 1102BR Amsterdam, The Netherlands. 
17
 * UK branch address: City House, 126-130 Hills Road, Cambridge CB2 1PQ, UK
18
 *
19
 * License: see the web/copyright.inc.php file in the file structure or
20
 *          <base_url>/copyright.php after deploying the software
21
 */
22
23
/**
24
 * This is the collection of methods dedicated for the user GUI
25
 * @author Tomasz Wolniewicz <[email protected]>
26
 * @author Stefan Winter <[email protected]>
27
 * @package UserAPI
28
 *
29
 * Parts of this code are based on simpleSAMLPhp discojuice module.
30
 * This product includes GeoLite data created by MaxMind, available from
31
 * http://www.maxmind.com
32
 */
33
34
namespace core;
35
36
use \Exception;
37
38
/**
39
 * The basic methoods for the user GUI
40
 * @package UserAPI
41
 *
42
 */
43
class UserAPI extends CAT
44
{
45
46
    /**
47
     * nothing special to be done here.
48
     */
49
    public function __construct()
50
    {
51
        parent::__construct();
52
    }
53
54
    /**
55
     * Prepare the device module environment and send back the link
56
     * This method creates a device module instance via the {@link DeviceFactory} call, 
57
     * then sets up the device module environment for the specific profile by calling 
58
     * {@link DeviceConfig::setup()} method and finally, called the devide writeInstaller meethod
59
     * passing the returned path name.
60
     * 
61
     * @param string $device       identifier as in {@link devices.php}
62
     * @param int    $profileId    profile identifier
63
     * @param string $generatedFor which download area does this pertain to
64
     * @param string $token        for silverbullet: invitation token to consume
65
     * @param string $password     for silverbull: import PIN for the future certificate
66
     *
67
     * @return array|NULL array with the following fields: 
68
     *  profile - the profile identifier; 
69
     *  device - the device identifier; 
70
     *  link - the path name of the resulting installer
71
     *  mime - the mimetype of the installer
72
     */
73
    public function generateInstaller($device, $profileId, $generatedFor = "user", $token = NULL, $password = NULL)
74
    {
75
        $this->loggerInstance->debug(4, "installer:$device:$profileId\n");
76
        $validator = new \web\lib\common\InputValidation();
77
        $profile = $validator->existingProfile($profileId);
78
        // test if the profile is production-ready and if not if the authenticated user is an owner
79
        if ($this->verifyDownloadAccess($profile) === FALSE) {
80
            return;
81
        }
82
        $installerProperties = [];
83
        $installerProperties['profile'] = $profileId;
84
        $installerProperties['device'] = $device;
85
        $cache = $this->getCache($device, $profile);
86
        $this->installerPath = $cache['path'];
87
        if ($this->installerPath !== NULL && $token === NULL && $password === NULL) {
88
            $this->loggerInstance->debug(4, "Using cached installer for: $device\n");
89
            $installerProperties['link'] = "API.php?action=downloadInstaller&lang=" . $this->languageInstance->getLang() . "&profile=$profileId&device=$device&generatedfor=$generatedFor";
90
            $installerProperties['mime'] = $cache['mime'];
91
        } else {
92
            $myInstaller = $this->generateNewInstaller($device, $profile, $generatedFor, $token, $password);
93
            if ($myInstaller['link'] !== 0) {
94
                $installerProperties['mime'] = $myInstaller['mime'];
95
            }
96
            $installerProperties['link'] = $myInstaller['link'];
97
        }
98
        return $installerProperties;
99
    }
100
101
    /**
102
     * checks whether the requested profile data is public, XOR was requested by
103
     * its own admin.
104
     * @param \core\AbstractProfile $profile the profile in question
105
     * @return boolean
106
     */
107
    private function verifyDownloadAccess($profile)
108
    {
109
        $attribs = $profile->getCollapsedAttributes();
110
        if (\core\common\Entity::getAttributeValue($attribs, 'profile:production', 0) !== 'on') {
111
            $this->loggerInstance->debug(4, "Attempt to download a non-production ready installer for profile: $profile->identifier\n");
112
            $auth = new \web\lib\admin\Authentication();
113
            if (!$auth->isAuthenticated()) {
114
                $this->loggerInstance->debug(2, "User NOT authenticated, rejecting request for a non-production installer\n");
115
                header("HTTP/1.0 403 Not Authorized");
116
                return FALSE;
117
            }
118
            $auth->authenticate();
119
            $userObject = new User($_SESSION['user']);
120
            if (!$userObject->isIdPOwner($profile->institution)) {
121
                $this->loggerInstance->debug(2, "User not an owner of a non-production profile - access forbidden\n");
122
                header("HTTP/1.0 403 Not Authorized");
123
                return FALSE;
124
            }
125
            $this->loggerInstance->debug(4, "User is the owner - allowing access\n");
126
        }
127
        return TRUE;
128
    }
129
130
    /**
131
     * This function tries to find a cached copy of an installer for a given
132
     * combination of Profile and device
133
     * 
134
     * @param string          $device  the device for which the installer is searched in cache
135
     * @param AbstractProfile $profile the profile for which the installer is searched in cache
136
     * @return array containing path to the installer and mime type of the file, the path is set to NULL if no cache can be returned
137
     */
138
    private function getCache($device, $profile)
139
    {
140
        $deviceConfig = \devices\Devices::listDevices()[$device];
141
        $noCache = (isset(\devices\Devices::$Options['no_cache']) && \devices\Devices::$Options['no_cache']) ? 1 : 0;
142
        if (isset($deviceConfig['options']['no_cache'])) {
143
            $noCache = $deviceConfig['options']['no_cache'] ? 1 : 0;
144
        }
145
        if ($noCache) {
146
            $this->loggerInstance->debug(5, "getCache: the no_cache option set for this device\n");
147
            return ['path' => NULL, 'mime' => NULL];
148
        }
149
        $this->loggerInstance->debug(5, "getCache: caching option set for this device\n");
150
        $cache = $profile->testCache($device);
151
        $iPath = $cache['cache'];
152
        if ($iPath && is_file($iPath)) {
153
            return ['path' => $iPath, 'mime' => $cache['mime']];
154
        }
155
        return ['path' => NULL, 'mime' => NULL];
156
    }
157
158
    /**
159
     * Generates a new installer for the given combination of device and Profile
160
     * 
161
     * @param string          $device       the device for which we want an installer
162
     * @param AbstractProfile $profile      the profile for which we want an installer
163
     * @param string          $generatedFor type of download requested (admin/user/silverbullet)
164
     * @param string          $token        in case of silverbullet, the token that was used to trigger the generation
165
     * @param string          $password     in case of silverbullet, the import PIN for the future client certificate
166
     * @return array info about the new installer (mime and link)
167
     */
168
    private function generateNewInstaller($device, $profile, $generatedFor, $token, $password)
169
    {
170
        $this->loggerInstance->debug(5, "generateNewInstaller() - Enter");
171
        $factory = new DeviceFactory($device);
172
        $this->loggerInstance->debug(5, "generateNewInstaller() - created Device");
173
        $dev = $factory->device;
174
        $out = [];
175
        if (isset($dev)) {
176
            $dev->setup($profile, $token, $password);
177
            $this->loggerInstance->debug(5, "generateNewInstaller() - Device setup done");
178
            $installer = $dev->writeInstaller();
179
            $this->loggerInstance->debug(5, "generateNewInstaller() - writeInstaller complete");
180
            $iPath = $dev->FPATH . '/tmp/' . $installer;
181
            if ($iPath && is_file($iPath)) {
182
                if (isset($dev->options['mime'])) {
183
                    $out['mime'] = $dev->options['mime'];
184
                } else {
185
                    $info = new \finfo();
186
                    $out['mime'] = $info->file($iPath, FILEINFO_MIME_TYPE);
187
                }
188
                $this->installerPath = $dev->FPATH . '/' . $installer;
189
                rename($iPath, $this->installerPath);
190
                $integerEap = (new \core\common\EAP($dev->selectedEap))->getIntegerRep();
191
                $profile->updateCache($device, $this->installerPath, $out['mime'], $integerEap);
192
                if (\config\Master::DEBUG_LEVEL < 4) {
193
                    \core\common\Entity::rrmdir($dev->FPATH . '/tmp');
194
                }
195
                $this->loggerInstance->debug(4, "Generated installer: " . $this->installerPath . ": for: $device, EAP:" . $integerEap . "\n");
196
                $out['link'] = "API.php?action=downloadInstaller&lang=" . $this->languageInstance->getLang() . "&profile=" . $profile->identifier . "&device=$device&generatedfor=$generatedFor";
197
            } else {
198
                $this->loggerInstance->debug(2, "Installer generation failed for: " . $profile->identifier . ":$device:" . $this->languageInstance->getLang() . "\n");
199
                $out['link'] = 0;
200
            }
201
        }
202
        return $out;
203
    }
204
205
    /**
206
     * interface to Devices::listDevices() 
207
     * 
208
     * @param int $showHidden whether or not hidden devices should be shown
209
     * @return array the list of devices
210
     * @throws Exception
211
     */
212
    public function listDevices($showHidden = 0)
213
    {
214
        $returnList = [];
215
        $count = 0;
216
        if ($showHidden !== 0 && $showHidden != 1) {
217
            throw new Exception("show_hidden is only be allowed to be 0 or 1, but it is $showHidden!");
218
        }
219
        foreach (\devices\Devices::listDevices() as $device => $deviceProperties) {
220
            if (\core\common\Entity::getAttributeValue($deviceProperties, 'options', 'hidden') === 1 && $showHidden === 0) {
221
                continue;
222
            }
223
            $count++;
224
            $deviceProperties['device'] = $device;
225
            $group = isset($deviceProperties['group']) ? $deviceProperties['group'] : 'other';
226
            if (!isset($returnList[$group])) {
227
                $returnList[$group] = [];
228
            }
229
            $returnList[$group][$device] = $deviceProperties;
230
        }
231
        return $returnList;
232
    }
233
234
    /**
235
     * 
236
     * @param string $device    identifier of the device
237
     * @param int    $profileId identifier of the profile
238
     * @return void
239
     */
240
    public function deviceInfo($device, $profileId)
241
    {
242
        $validator = new \web\lib\common\InputValidation();
243
        $out = 0;
244
        $profile = $validator->existingProfile($profileId);
245
        $factory = new DeviceFactory($device);
246
        $dev = $factory->device;
247
        if (isset($dev)) {
248
            $dev->setup($profile);
249
            $out = $dev->writeDeviceInfo();
250
        }
251
        echo $out;
252
    }
253
254
    /**
255
     * Prepare the support data for a given profile
256
     *
257
     * @param int $profId profile identifier
258
     * @return array
259
     * array with the following fields:
260
     * - local_email
261
     * - local_phone
262
     * - local_url
263
     * - description
264
     * - devices - an array of device names and their statuses (for a given profile)
265
     * - last_changed
266
     */
267
    public function profileAttributes($profId)
268
    {
269
        $validator = new \web\lib\common\InputValidation();
270
        $profile = $validator->existingProfile($profId);
271
        $attribs = $profile->getCollapsedAttributes();
272
        $returnArray = [];
273
        $returnArray['silverbullet'] = $profile instanceof ProfileSilverbullet ? 1 : 0;
274
        if (isset($attribs['support:email'])) {
275
            $returnArray['local_email'] = $attribs['support:email'][0];
276
        }
277
        if (isset($attribs['support:phone'])) {
278
            $returnArray['local_phone'] = $attribs['support:phone'][0];
279
        }
280
        if (isset($attribs['support:url'])) {
281
            $returnArray['local_url'] = $attribs['support:url'][0];
282
        }
283
        if (isset($attribs['profile:description'])) {
284
            $returnArray['description'] = $attribs['profile:description'][0];
285
        }
286
        if (isset($attribs['media:openroaming'])) {
287
            $returnArray['openroaming'] = $attribs['media:openroaming'][0];
288
        }
289
        $returnArray['devices'] = $profile->listDevices();
290
        $returnArray['last_changed'] = $profile->getFreshness();
291
        return $returnArray;
292
    }
293
294
    /**
295
     * Generate and send the installer
296
     *
297
     * @param string $device        identifier as in {@link devices.php}
298
     * @param int    $prof_id       profile identifier
299
     * @param string $generated_for which download area does this pertain to
300
     * @param string $token         for silverbullet: invitation token to consume
301
     * @param string $password      for silverbull: import PIN for the future certificate
302
     * @return string binary stream: installerFile
303
     */
304
    public function downloadInstaller($device, $prof_id, $generated_for = 'user', $token = NULL, $password = NULL)
305
    {
306
        $this->loggerInstance->debug(4, "downloadInstaller arguments: $device,$prof_id,$generated_for\n");
307
        $output = $this->generateInstaller($device, $prof_id, $generated_for, $token, $password);
308
        $this->loggerInstance->debug(4, "output from GUI::generateInstaller:");
309
        $this->loggerInstance->debug(4, print_r($output, true));
310
        if (empty($output['link']) || $output['link'] === 0) {
311
            header("HTTP/1.0 404 Not Found");
312
            return;
313
        }
314
        $validator = new \web\lib\common\InputValidation();
315
        $profile = $validator->existingProfile($prof_id);
316
        $profile->incrementDownloadStats($device, $generated_for);
317
        $file = $this->installerPath;
318
        $filetype = $output['mime'];
319
        $this->loggerInstance->debug(4, "installer MIME type:$filetype\n");
320
        header("Content-type: " . $filetype);
0 ignored issues
show
Security Response Splitting introduced by
'Content-type: ' . $filetype can contain request data and is used in response header context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. Read from $_GET, and InputValidation::existingProfile() is called
    in web/admin/overview_installers.php on line 37
  2. Enters via parameter $input
    in web/lib/common/InputValidation.php on line 119
  3. array('w10' => array('group' => 'microsoft', 'display' => _('MS Windows 10'), 'match' => 'Windows NT 10', 'directory' => 'ms', 'module' => 'W8W10', 'signer' => 'ms_windows_sign', 'options' => array('sign' => 1, 'device_id' => 'W10', 'clientcert' => devices\Devices::SUPPORT_EMBEDDED_ECDSA, 'mime' => 'application/x-dosexec')), 'w8' => array('group' => 'microsoft', 'display' => _('MS Windows 8, 8.1'), 'match' => 'Windows NT 6[._][23]', 'directory' => 'ms', 'module' => 'W8W10', 'signer' => 'ms_windows_sign', 'options' => array('sign' => 1, 'device_id' => 'W8', 'clientcert' => devices\Devices::SUPPORT_EMBEDDED_ECDSA, 'mime' => 'application/x-dosexec')), 'w7' => array('group' => 'microsoft', 'display' => _('MS Windows 7'), 'match' => 'Windows NT 6[._]1', 'directory' => 'ms', 'module' => 'Vista7', 'signer' => 'ms_windows_sign', 'options' => array('sign' => 1, 'device_id' => 'W7', 'mime' => 'application/x-dosexec')), 'vista' => array('group' => 'microsoft', 'display' => _('MS Windows Vista'), 'match' => 'Windows NT 6[._]0', 'directory' => 'ms', 'module' => 'Vista7', 'signer' => 'ms_windows_sign', 'options' => array('sign' => 1, 'device_id' => 'Vista', 'mime' => 'application/x-dosexec')), 'win-rt' => array('group' => 'microsoft', 'display' => _('Windows RT'), 'directory' => 'redirect_dev', 'module' => 'RedirectDev', 'options' => array('hidden' => 0, 'redirect' => 1)), 'apple_global' => array('group' => 'apple', 'display' => _('Apple device'), 'match' => '(Mac OS X 1[01][._][0-9])|((iPad|iPhone|iPod);.*OS (\d+)_)', 'directory' => 'apple_mobileconfig', 'module' => 'MobileconfigOsX', 'signer' => 'mobileconfig_sign', 'options' => array('sign' => 1, 'device_id' => 'OS_X', 'mime' => 'application/x-apple-aspen-config', 'clientcert' => devices\Devices::SUPPORT_EMBEDDED_ECDSA, 'sb_message' => _('During the installation you will be first asked to enter settings for certificate and there you need to enter the import PIN shown on this page. Later you will be prompted to enter your password to allow making changes to the profile, this time it is your computer password.'))), 'apple_catalina' => array('group' => 'apple', 'display' => _('Apple macOS Catalina'), 'match' => 'Mac OS X 10[._]15', 'directory' => 'apple_mobileconfig', 'module' => 'MobileconfigOsX', 'signer' => 'mobileconfig_sign', 'options' => array('hidden' => 1, 'sign' => 1, 'device_id' => 'OS_X', 'mime' => 'application/x-apple-aspen-config', 'clientcert' => devices\Devices::SUPPORT_EMBEDDED_ECDSA, 'sb_message' => _('During the installation you will be first asked to enter settings for certificate and there you need to enter the import PIN shown on this page. Later you will be prompted to enter your password to allow making changes to the profile, this time it is your computer password.'))), 'apple_mojave' => array('group' => 'apple', 'display' => _('Apple macOS Mojave'), 'match' => 'Mac OS X 10[._]14', 'directory' => 'apple_mobileconfig', 'module' => 'MobileconfigOsX', 'signer' => 'mobileconfig_sign', 'options' => array('hidden' => 1, 'sign' => 1, 'device_id' => 'OS_X', 'mime' => 'application/x-apple-aspen-config', 'clientcert' => devices\Devices::SUPPORT_EMBEDDED_ECDSA, 'sb_message' => _('During the installation you will be first asked to enter settings for certificate and there you need to enter the import PIN shown on this page. Later you will be prompted to enter your password to allow making changes to the profile, this time it is your computer password.'))), 'apple_hi_sierra' => array('group' => 'apple', 'display' => _('Apple macOS High Sierra'), 'match' => 'Mac OS X 10[._]13', 'directory' => 'apple_mobileconfig', 'module' => 'MobileconfigOsX', 'signer' => 'mobileconfig_sign', 'options' => array('hidden' => 1, 'sign' => 1, 'device_id' => 'OS_X', 'mime' => 'application/x-apple-aspen-config', 'clientcert' => devices\Devices::SUPPORT_EMBEDDED_ECDSA, 'sb_message' => _('During the installation you will be first asked to enter settings for certificate and there you need to enter the import PIN shown on this page. Later you will be prompted to enter your password to allow making changes to the profile, this time it is your computer password.'))), 'apple_sierra' => array('group' => 'apple', 'display' => _('Apple macOS Sierra'), 'match' => 'Mac OS X 10[._]12', 'directory' => 'apple_mobileconfig', 'module' => 'MobileconfigOsX', 'signer' => 'mobileconfig_sign', 'options' => array('hidden' => 1, 'sign' => 1, 'device_id' => 'OS_X', 'mime' => 'application/x-apple-aspen-config', 'sb_message' => _('During the installation you will be first asked to enter settings for certificate and there you need to enter the import PIN shown on this page. Later you will be prompted to enter your password to allow making changes to the profile, this time it is your computer password.'))), 'apple_el_cap' => array('group' => 'apple', 'display' => _('Apple OS X El Capitan'), 'match' => 'Mac OS X 10[._]11', 'directory' => 'apple_mobileconfig', 'module' => 'MobileconfigOsX', 'signer' => 'mobileconfig_sign', 'options' => array('hidden' => 1, 'sign' => 1, 'device_id' => 'OS_X', 'mime' => 'application/x-apple-aspen-config', 'sb_message' => _('During the installation you will be first asked to enter settings for certificate and there you need to enter the import PIN shown on this page. Later you will be prompted to enter your password to allow making changes to the profile, this time it is your computer password.'))), 'apple_yos' => array('group' => 'apple', 'display' => _('Apple OS X Yosemite'), 'match' => 'Mac OS X 10[._]10', 'directory' => 'apple_mobileconfig', 'module' => 'MobileconfigOsX', 'signer' => 'mobileconfig_sign', 'options' => array('hidden' => 1, 'sign' => 1, 'device_id' => 'OS_X', 'mime' => 'application/x-apple-aspen-config', 'sb_message' => _('During the installation you will be first asked to enter settings for certificate and there you need to enter the import PIN shown on this page. Later you will be prompted to enter your password to allow making changes to the profile, this time it is your computer password.'))), 'apple_mav' => array('group' => 'apple', 'display' => _('Apple OS X Mavericks'), 'match' => 'Mac OS X 10[._]9', 'directory' => 'apple_mobileconfig', 'module' => 'MobileconfigOsX', 'signer' => 'mobileconfig_sign', 'options' => array('hidden' => 1, 'sign' => 1, 'device_id' => 'OS_X', 'mime' => 'application/x-apple-aspen-config', 'sb_message' => _('During the installation you will be first asked to enter settings for certificate and there you need to enter the import PIN shown on this page. Later you will be prompted to enter your password to allow making changes to the profile, this time it is your computer password.'))), 'apple_m_lion' => array('group' => 'apple', 'display' => _('Apple OS X Mountain Lion'), 'match' => 'Mac OS X 10[._]8', 'directory' => 'apple_mobileconfig', 'module' => 'MobileconfigOsX', 'signer' => 'mobileconfig_sign', 'options' => array('hidden' => 1, 'sign' => 1, 'device_id' => 'OS_X', 'mime' => 'application/x-apple-aspen-config', 'sb_message' => _('During the installation you will be first asked to enter settings for certificate and there you need to enter the import PIN shown on this page. Later you will be prompted to enter your password to allow making changes to the profile, this time it is your computer password.'))), 'apple_lion' => array('group' => 'apple', 'display' => _('Apple OS X Lion'), 'match' => 'Mac OS X 10[._]7', 'directory' => 'apple_mobileconfig', 'module' => 'MobileconfigOsX', 'signer' => 'mobileconfig_sign', 'options' => array('hidden' => 1, 'sign' => 1, 'device_id' => 'OS_X', 'mime' => 'application/x-apple-aspen-config', 'sb_message' => _('During the installation you will be first asked to enter settings for certificate and there you need to enter the import PIN shown on this page. Later you will be prompted to enter your password to allow making changes to the profile, this time it is your computer password.'))), 'mobileconfig' => array('group' => 'apple', 'display' => _('Apple iOS mobile device (iOS 7-11)'), 'match' => '(iPad|iPhone|iPod);.*OS ([7-9]|1[0-1])_', 'directory' => 'apple_mobileconfig', 'module' => 'MobileconfigIos7plus', 'signer' => 'mobileconfig_sign', 'options' => array('hidden' => 1, 'sign' => 1, 'device_id' => 'iOS', 'mime' => 'application/x-apple-aspen-config', 'sb_message' => _('During the installation you will be first asked to enter your passcode - this is your device security code! Later on you will be prompted for the password to the certificate and there you need to enter the import PIN shown on this page.'))), 'mobileconfig-56' => array('group' => 'apple', 'display' => _('Apple iOS mobile device (iOS 5 and 6)'), 'match' => '(iPad|iPhone|iPod);.*OS [56]_', 'directory' => 'apple_mobileconfig', 'module' => 'MobileconfigIos5plus', 'signer' => 'mobileconfig_sign', 'options' => array('hidden' => 1, 'sign' => 1, 'device_id' => 'iOS', 'mime' => 'application/x-apple-aspen-config')), 'linux' => array('group' => 'linux', 'display' => _('Linux'), 'match' => 'Linux(?!.*Android)', 'directory' => 'linux', 'module' => 'Linux', 'options' => array('mime' => 'application/x-sh')), 'chromeos' => array('group' => 'chrome', 'display' => _('Chrome OS'), 'match' => 'CrOS', 'directory' => 'chromebook', 'module' => 'Chromebook', 'options' => array('mime' => 'application/x-onc', 'message' => sprintf(_('After downloading the file, open the Chrome browser and browse to this URL: <a href='chrome://net-internals/#chromeos'>chrome://net-internals/#chromeos</a>. Then, use the 'Import ONC file' button. The import is silent; the new network definitions will be added to the preferred networks.')))), 'android_recent' => array('group' => 'android', 'display' => _('Android 11 and higher'), 'match' => 'Android 1[1-9]', 'directory' => 'xml', 'module' => 'Lollipop', 'options' => array('mime' => 'application/eap-config', 'message' => sprintf(_('Before you proceed with installation on Android systems, please make sure that you have installed the %s application. This application is available from these sites: %s and will use the configuration file downloaded from CAT to create all necessary settings.'), 'geteduroam', '<a target='_blank' href='https://play.google.com/store/apps/details?id=app.eduroam.geteduroam'>Google Play</a>, <a target='_blank' href='geteduroam-stable.apk'>' . _('as local download') . '</a>'))), 'android_8_10' => array('group' => 'android', 'display' => _('Android 8 to 10'), 'match' => 'Android ([89]|10)', 'directory' => 'xml', 'module' => 'Lollipop', 'options' => array('mime' => 'application/eap-config', 'message' => sprintf(_('Before you proceed with installation on Android systems, please make sure that you have installed the %s application. This application is available from these sites: %s and will use the configuration file downloaded from CAT to create all necessary settings.'), 'geteduroam', '<a target='_blank' href='https://play.google.com/store/apps/details?id=app.eduroam.geteduroam'>Google Play</a>, <a target='_blank' href='geteduroam-stable.apk'>' . _('as local download') . '</a>'))), 'android_4_7' => array('group' => 'android', 'display' => _('Android 4.3 to 7'), 'match' => 'Android [4-7]', 'directory' => 'xml', 'module' => 'Lollipop', 'options' => array('mime' => 'application/eap-config', 'message' => sprintf(_('Before you proceed with installation on Android systems, please make sure that you have installed the %s application. This application is available from these sites: %s and will use the configuration file downloaded from CAT to create all necessary settings.'), 'eduroamCAT', '<a target='_blank' href='https://play.google.com/store/apps/details?id=uk.ac.swansea.eduroamcat'>Google Play</a>, <a target='_blank' href='https://www.amazon.com/dp/B01EACCX0S/'>Amazon Appstore</a>, <a target='_blank' href='eduroamCAT-stable.apk'>' . _('as local download') . '</a>'))), 'android_q' => array('group' => 'android', 'display' => _('Android 10.0 Q'), 'match' => 'Android 10', 'directory' => 'xml', 'module' => 'Lollipop', 'options' => array('hidden' => 1, 'mime' => 'application/eap-config', 'message' => sprintf(_('Before you proceed with installation on Android systems, please make sure that you have installed the %s application. This application is available from these sites: %s and will use the configuration file downloaded from CAT to create all necessary settings.'), 'eduroamCAT', '<a target='_blank' href='https://play.google.com/store/apps/details?id=uk.ac.swansea.eduroamcat'>Google Play</a>, <a target='_blank' href='https://www.amazon.com/dp/B01EACCX0S/'>Amazon Appstore</a>, <a target='_blank' href='eduroamCAT-stable.apk'>' . _('as local download') . '</a>'))), 'android_pie' => array('group' => 'android', 'display' => _('Android 9.0 Pie'), 'match' => 'Android 9', 'directory' => 'xml', 'module' => 'Lollipop', 'options' => array('hidden' => 1, 'mime' => 'application/eap-config', 'message' => sprintf(_('Before you proceed with installation on Android systems, please make sure that you have installed the %s application. This application is available from these sites: %s and will use the configuration file downloaded from CAT to create all necessary settings.'), 'eduroamCAT', '<a target='_blank' href='https://play.google.com/store/apps/details?id=uk.ac.swansea.eduroamcat'>Google Play</a>, <a target='_blank' href='https://www.amazon.com/dp/B01EACCX0S/'>Amazon Appstore</a>, <a target='_blank' href='eduroamCAT-stable.apk'>' . _('as local download') . '</a>'))), 'android_oreo' => array('group' => 'android', 'display' => _('Android 8.0 Oreo'), 'match' => 'Android 8', 'directory' => 'xml', 'module' => 'Lollipop', 'options' => array('hidden' => 1, 'mime' => 'application/eap-config', 'message' => sprintf(_('Before you proceed with installation on Android systems, please make sure that you have installed the %s application. This application is available from these sites: %s and will use the configuration file downloaded from CAT to create all necessary settings.'), 'eduroamCAT', '<a target='_blank' href='https://play.google.com/store/apps/details?id=uk.ac.swansea.eduroamcat'>Google Play</a>, <a target='_blank' href='https://www.amazon.com/dp/B01EACCX0S/'>Amazon Appstore</a>, <a target='_blank' href='eduroamCAT-stable.apk'>' . _('as local download') . '</a>'))), 'android_nougat' => array('group' => 'android', 'display' => _('Android 7.0 Nougat'), 'match' => 'Android 7', 'directory' => 'xml', 'module' => 'Lollipop', 'options' => array('hidden' => 1, 'mime' => 'application/eap-config', 'message' => sprintf(_('Before you proceed with installation on Android systems, please make sure that you have installed the %s application. This application is available from these sites: %s and will use the configuration file downloaded from CAT to create all necessary settings.'), 'eduroamCAT', '<a target='_blank' href='https://play.google.com/store/apps/details?id=uk.ac.swansea.eduroamcat'>Google Play</a>, <a target='_blank' href='https://www.amazon.com/dp/B01EACCX0S/'>Amazon Appstore</a>, <a target='_blank' href='eduroamCAT-stable.apk'>' . _('as local download') . '</a>'))), 'android_marshmallow' => array('group' => 'android', 'display' => _('Android 6.0 Marshmallow'), 'match' => 'Android 6', 'directory' => 'xml', 'module' => 'Lollipop', 'options' => array('hidden' => 1, 'mime' => 'application/eap-config', 'message' => sprintf(_('Before you proceed with installation on Android systems, please make sure that you have installed the %s application. This application is available from these sites: %s and will use the configuration file downloaded from CAT to create all necessary settings.'), 'eduroamCAT', '<a target='_blank' href='https://play.google.com/store/apps/details?id=uk.ac.swansea.eduroamcat'>Google Play</a>, <a target='_blank' href='https://www.amazon.com/dp/B01EACCX0S/'>Amazon Appstore</a>, <a target='_blank' href='eduroamCAT-stable.apk'>' . _('as local download') . '</a>'))), 'android_lollipop' => array('group' => 'android', 'display' => _('Android 5.0 Lollipop'), 'match' => 'Android 5', 'directory' => 'xml', 'module' => 'Lollipop', 'options' => array('hidden' => 1, 'mime' => 'application/eap-config', 'message' => sprintf(_('Before you proceed with installation on Android systems, please make sure that you have installed the %s application. This application is available from these sites: %s and will use the configuration file downloaded from CAT to create all necessary settings.'), 'eduroamCAT', '<a target='_blank' href='https://play.google.com/store/apps/details?id=uk.ac.swansea.eduroamcat'>Google Play</a>, <a target='_blank' href='https://www.amazon.com/dp/B01EACCX0S/'>Amazon Appstore</a>, <a target='_blank' href='eduroamCAT-stable.apk'>' . _('as local download') . '</a>'))), 'android_kitkat' => array('group' => 'android', 'display' => _('Android 4.4 KitKat'), 'match' => 'Android 4\.[4-9]', 'directory' => 'xml', 'module' => 'KitKat', 'options' => array('hidden' => 1, 'mime' => 'application/eap-config', 'message' => sprintf(_('Before you proceed with installation on Android systems, please make sure that you have installed the %s application. This application is available from these sites: %s and will use the configuration file downloaded from CAT to create all necessary settings.'), 'eduroamCAT', '<a target='_blank' href='https://play.google.com/store/apps/details?id=uk.ac.swansea.eduroamcat'>Google Play</a>, <a target='_blank' href='https://www.amazon.com/dp/B01EACCX0S/'>Amazon Appstore</a>, <a target='_blank' href='eduroamCAT-stable.apk'>' . _('as local download') . '</a>'))), 'android_43' => array('group' => 'android', 'display' => _('Android 4.3'), 'match' => 'Android 4\.3', 'directory' => 'xml', 'module' => 'KitKat', 'options' => array('hidden' => 1, 'mime' => 'application/eap-config', 'message' => sprintf(_('Before you proceed with installation on Android systems, please make sure that you have installed the %s application. This application is available from these sites: %s and will use the configuration file downloaded from CAT to create all necessary settings.'), 'eduroamCAT', '<a target='_blank' href='https://play.google.com/store/apps/details?id=uk.ac.swansea.eduroamcat'>Google Play</a>, <a target='_blank' href='https://www.amazon.com/dp/B01EACCX0S/'>Amazon Appstore</a>, <a target='_blank' href='eduroamCAT-stable.apk'>' . _('as local download') . '</a>'))), 'android_legacy' => array('group' => 'android', 'display' => _('Android'), 'match' => 'Android', 'directory' => 'redirect_dev', 'module' => 'RedirectDev', 'options' => array('redirect' => 1)), 'eap-config' => array('group' => 'eap-config', 'display' => _('EAP config'), 'directory' => 'xml', 'module' => 'XMLAll', 'options' => array('mime' => 'application/eap-config', 'message' => sprintf(_('This option provides an EAP config XML file, which can be consumed by the eduroamCAT app for Android.')))), 'eap-generic' => array('group' => 'eap-config', 'display' => _('EAP generic'), 'directory' => 'xml', 'module' => 'Generic', 'options' => array('mime' => 'application/eap-config', 'message' => sprintf(_('This option provides a generic EAP config XML file, which can be consumed by the GetEduroam applications.')), 'hidden' => 1)), 'test' => array('group' => 'other', 'display' => _('Test'), 'directory' => 'test_module', 'module' => 'TestModule', 'options' => array('hidden' => 1))) is assigned to $retArray
    in devices/Devices-template.php on line 125
  4. $retArray is returned
    in devices/Devices-template.php on line 649
  5. devices\Devices::listDevices() is assigned to $Dev
    in core/DeviceFactory.php on line 66
  6. $Dev[$blueprint]['options'] is assigned to $Opt
    in core/DeviceFactory.php on line 83
  7. $Opt is assigned to $value
    in core/DeviceFactory.php on line 84
  8. $value is assigned to $options
    in core/DeviceFactory.php on line 85
  9. $options is assigned to property DeviceConfig::$options
    in core/DeviceFactory.php on line 88
  10. Read from property DeviceConfig::$options, and $dev->options['mime'] is assigned to $out
    in core/UserAPI.php on line 183
  11. $out is returned
    in core/UserAPI.php on line 202
  12. $this->generateNewInstaller($device, $profile, $generatedFor, $token, $password) is assigned to $myInstaller
    in core/UserAPI.php on line 92
  13. $myInstaller['mime'] is assigned to $installerProperties
    in core/UserAPI.php on line 94
  14. $installerProperties is returned
    in core/UserAPI.php on line 98
  15. $this->generateInstaller($device, $prof_id, $generated_for, $token, $password) is assigned to $output
    in core/UserAPI.php on line 307
  16. $output['mime'] is assigned to $filetype
    in core/UserAPI.php on line 318

Response Splitting Attacks

Allowing an attacker to set a response header, opens your application to response splitting attacks; effectively allowing an attacker to send any response, he would like.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
321
        if ($filetype !== "application/x-wifi-config") { // for those installers to work on Android, Content-Disposition MUST NOT be set
322
            header('Content-Disposition: inline; filename="' . basename($file) . '"');
323
        } else {
324
            header('Content-Transfer-Encoding: base64');
325
        }
326
        header('Content-Length: ' . filesize($file));
327
        ob_clean();
328
        flush();
329
        readfile($file);
330
    }
331
332
    /**
333
     * resizes image files
334
     * 
335
     * @param string $inputImage the image we want to process
336
     * @param string $destFile   the output file for the processed image
337
     * @param int    $width      if resizing, the target width
338
     * @param int    $height     if resizing, the target height
339
     * @param bool   $resize     shall we do resizing? width and height are ignored otherwise
340
     * @return array
341
     */
342
    private function processImage($inputImage, $destFile, $width, $height, $resize)
343
    {
344
        $info = new \finfo();
345
        $filetype = $info->buffer($inputImage, FILEINFO_MIME_TYPE);
346
        $offset = 60 * 60 * 24 * 30;
347
        // gmdate cannot fail here - time() is its default argument (and integer), and we are adding an integer to it
348
        $expiresString = "Expires: " . /** @scrutinizer ignore-type */ gmdate("D, d M Y H:i:s", time() + $offset) . " GMT";
349
        $blob = $inputImage;
350
351
        if ($resize === TRUE) {
352
            $image = new \Imagick();
353
            $image->readImageBlob($inputImage);
354
            $image->setImageFormat('PNG');
355
            $image->thumbnailImage($width, $height, 1);
356
            $blob = $image->getImageBlob();
357
            $this->loggerInstance->debug(4, "Writing cached logo $destFile for IdP/Federation.\n");
358
            file_put_contents($destFile, $blob);
359
        }
360
361
        return ["filetype" => $filetype, "expires" => $expiresString, "blob" => $blob];
362
    }
363
364
    /**
365
     * Get and prepare logo file 
366
     *
367
     * When called for DiscoJuice, first check if file cache exists
368
     * If not then generate the file and save it in the cache
369
     * @param int|string $identifier IdP or Federation identifier
370
     * @param string     $type       either 'idp' or 'federation' is allowed 
371
     * @param integer    $widthIn    maximum width of the generated image - if 0 then it is treated as no upper bound
372
     * @param integer    $heightIn   maximum height of the generated image - if 0 then it is treated as no upper bound
373
     * @return array|null array with image information or NULL if there is no logo
374
     * @throws Exception
375
     */
376
    protected function getLogo($identifier, $type, $widthIn, $heightIn)
377
    {
378
        $expiresString = '';
379
        $attributeName = [
380
            'federation' => "fed:logo_file",
381
            'federation_from_idp' => "fed:logo_file",
382
            'idp' => "general:logo_file",
383
        ];
384
385
        $logoFile = "";
386
        $validator = new \web\lib\common\InputValidation();
387
        switch ($type) {
388
            case "federation":
389
                $entity = $validator->existingFederation($identifier);
390
                break;
391
            case "idp":
392
                $entity = $validator->existingIdP($identifier);
393
                break;
394
            case "federation_from_idp":
395
                $idp = $validator->existingIdP($identifier);
396
                $entity = $validator->existingFederation($idp->federation);
397
                break;
398
            default:
399
                throw new Exception("Unknown type of logo requested!");
400
        }
401
        $filetype = 'image/png'; // default, only one code path where it can become different
402
        list($width, $height, $resize) = $this->testForResize($widthIn, $heightIn);
403
        if ($resize) {
404
            $logoFile = ROOT . '/web/downloads/logos/' . $identifier . '_' . $width . '_' . $height . '.png';
405
        }
406
        if (is_file($logoFile)) { // $logoFile could be an empty string but then we will get a FALSE
407
            $this->loggerInstance->debug(4, "Using cached logo $logoFile for: $identifier\n");
408
            $blob = file_get_contents($logoFile);
409
        } else {
410
            $logoAttribute = $entity->getAttributes($attributeName[$type]);
411
            if (count($logoAttribute) == 0) {
412
                return NULL;
413
            }
414
            $this->loggerInstance->debug(4, "RESIZE:$width:$height\n");
415
            $meta = $this->processImage($logoAttribute[0]['value'], $logoFile, $width, $height, $resize);
416
            $filetype = $meta['filetype'];
417
            $expiresString = $meta['expires'];
418
            $blob = $meta['blob'];
419
        }
420
        return ["filetype" => $filetype, "expires" => $expiresString, "blob" => $blob];
421
    }
422
423
    /**
424
     * see if we have to resize an image
425
     * 
426
     * @param integer $width  the desired max width (0 = unbounded)
427
     * @param integer $height the desired max height (0 = unbounded)
428
     * @return array
429
     */
430
    private function testForResize($width, $height)
431
    {
432
        if (is_numeric($width) && is_numeric($height) && ($width > 0 || $height > 0)) {
433
            if ($height == 0) {
434
                $height = 10000;
435
            }
436
            if ($width == 0) {
437
                $width = 10000;
438
            }
439
            return [$width, $height, TRUE];
440
        }
441
        return [0, 0, FALSE];
442
    }
443
444
    /**
445
     * find out where the device is currently located
446
     * @return array
447
     */
448
    public function locateDevice()
449
    {
450
        return \core\DeviceLocation::locateDevice();
451
    }
452
453
    /**
454
     * Lists all identity providers in the database
455
     * adding information required by DiscoJuice.
456
     * 
457
     * @param int    $activeOnly if set to non-zero will cause listing of only those institutions which have some valid profiles defined.
458
     * @param string $country    if set, only list IdPs in a specific country
459
     * @return array the list of identity providers
460
     *
461
     */
462
    public function listAllIdentityProviders($activeOnly = 0, $country = "")
463
    {
464
        return IdPlist::listAllIdentityProviders($activeOnly, $country);
465
    }
466
467
    /**
468
     * Order active identity providers according to their distance and name
469
     * @param string $country         NRO to work with
470
     * @param array  $currentLocation current location
471
     *
472
     * @return array $IdPs -  list of arrays ('id', 'name');
473
     */
474
    public function orderIdentityProviders($country, $currentLocation)
475
    {
476
        return IdPlist::orderIdentityProviders($country, $currentLocation);
477
    }
478
    
479
    /**
480
     * outputs a full list of IdPs containing the fllowing data:
481
     * institution_is, institution name in all available languages,
482
     * list of production profiles.
483
     * For eache profile the profile identifier, profile name in all languages
484
     * and redirect values (empty rediret value means that no redirect has been
485
     * set).
486
     * 
487
     * @return array of identity providers with attributes
488
     */
489
    public function listIdentityProvidersWithProfiles() {
490
        return IdPlist::listIdentityProvidersWithProfiles();
491
    }
492
    
493
    /**
494
     * Detect the best device driver form the browser
495
     * Detects the operating system and returns its id 
496
     * display name and group membership (as in devices.php)
497
     * @return array|boolean OS information, indexed by 'id', 'display', 'group'
498
     */
499
    public function detectOS()
500
    {
501
        $Dev = \devices\Devices::listDevices();
502
        $devId = $this->deviceFromRequest();
503
        if ($devId !== NULL) {
504
            $ret = $this->returnDevice($devId, $Dev[$devId]);
505
            if ($ret !== FALSE) {
506
                return $ret;
507
            }
508
        }
509
// the device has not been specified or not specified correctly, try to detect if from the browser ID
510
        $browser = filter_input(INPUT_SERVER, 'HTTP_USER_AGENT', FILTER_SANITIZE_STRING);
511
        $this->loggerInstance->debug(4, "HTTP_USER_AGENT=$browser\n");
512
        foreach ($Dev as $devId => $device) {
513
            if (!isset($device['match'])) {
514
                continue;
515
            }
516
            if (preg_match('/' . $device['match'] . '/', $browser)) {
517
                return $this->returnDevice($devId, $device);
518
            }
519
        }
520
        $this->loggerInstance->debug(2, "Unrecognised system: $browser\n");
521
        return FALSE;
522
    }
523
524
    /**
525
     * test if devise is defined and is not hidden. If all is fine return extracted information.
526
     * 
527
     * @param string $devId  device id as defined as index in Devices.php
528
     * @param array  $device device info as defined in Devices.php
529
     * @return array|FALSE if the device has not been correctly specified
530
     */
531
    private function returnDevice($devId, $device)
532
    {
533
        if (\core\common\Entity::getAttributeValue($device, 'options', 'hidden') !== 1) {
534
            $this->loggerInstance->debug(4, "Browser_id: $devId\n");
535
            return ['device' => $devId, 'display' => $device['display'], 'group' => $device['group']];
536
        }
537
        return FALSE;
538
    }
539
540
    /**
541
     * This methods cheks if the devide has been specified as the HTTP parameters
542
     * 
543
     * @return device id|NULL if correcty specified or FALSE otherwise
544
     */
545
    private function deviceFromRequest()
546
    {
547
        $devId = filter_input(INPUT_GET, 'device', FILTER_SANITIZE_STRING) ?? filter_input(INPUT_POST, 'device', FILTER_SANITIZE_STRING);
548
        if ($devId === NULL || $devId === FALSE) {
549
            $this->loggerInstance->debug(2, "Invalid device id provided\n");
550
            return NULL;
551
        }
552
        if (!isset(\devices\Devices::listDevices()[$devId])) {
553
            $this->loggerInstance->debug(2, "Unrecognised system: $devId\n");
554
            return NULL;
555
        }
556
        return $devId;
557
    }
558
559
    /**
560
     * finds all the user certificates that originated in a given token
561
     * 
562
     * @param string $token the token for which we are fetching all associated user certs
563
     * @return array|boolean returns FALSE if a token is invalid, otherwise array of certs
564
     */
565
    public function getUserCerts($token)
566
    {
567
        $validator = new \web\lib\common\InputValidation();
568
        $cleanToken = $validator->token($token);
569
        if ($cleanToken) {
570
            // check status of this silverbullet token according to info in DB:
571
            // it can be VALID (exists and not redeemed, EXPIRED, REDEEMED or INVALID (non existent)
572
            $invitationObject = new \core\SilverbulletInvitation($cleanToken);
573
        } else {
574
            return false;
575
        }
576
        $profile = new \core\ProfileSilverbullet($invitationObject->profile, NULL);
577
        $userdata = $profile->userStatus($invitationObject->userId);
578
        $allcerts = [];
579
        foreach ($userdata as $content) {
580
            $allcerts = array_merge($allcerts, $content->associatedCertificates);
581
        }
582
        return $allcerts;
583
    }
584
585
    /**
586
     * device name
587
     * 
588
     * @var string
589
     */
590
    public $device;
591
592
    /**
593
     * path to installer
594
     * 
595
     * @var string
596
     */
597
    private $installerPath;
598
599
    /**
600
     * helper function to sort profiles by their name
601
     * @param \core\AbstractProfile $profile1 the first profile's information
602
     * @param \core\AbstractProfile $profile2 the second profile's information
603
     * @return int
604
     */
605
    private static function profileSort($profile1, $profile2)
606
    {
607
        return strcasecmp($profile1->name, $profile2->name);
608
    }
609
}