Completed
Push — master ( cc9aa9...547d73 )
by
unknown
04:54
created

Profile::getAttributes()   C

Complexity

Conditions 13
Paths 26

Size

Total Lines 39
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 2 Features 0
Metric Value
cc 13
eloc 22
c 3
b 2
f 0
nc 26
nop 3
dl 0
loc 39
rs 5.1234

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/* * ********************************************************************************
4
 * (c) 2011-15 GÉANT on behalf of the GN3, GN3plus and GN4 consortia
5
 * License: see the LICENSE file in the root directory
6
 * ********************************************************************************* */
7
?>
8
<?php
9
10
/**
11
 * This file contains the Profile class.
12
 *
13
 * @author Stefan Winter <[email protected]>
14
 * @author Tomasz Wolniewicz <[email protected]>
15
 *
16
 * @package Developer
17
 *
18
 */
19
/**
20
 * necessary includes
21
 */
22
require_once('Helper.php');
23
require_once('IdP.php');
24
require_once('EAP.php');
25
require_once('X509.php');
26
require_once('EntityWithDBProperties.php');
27
require_once('devices/devices.php');
28
29
define("HIDDEN", -1);
30
define("AVAILABLE", 0);
31
define("UNAVAILABLE", 1);
32
define("INCOMPLETE", 2);
33
define("NOTCONFIGURED", 3);
34
35
/**
36
 * This class represents an EAP Profile.
37
 * Profiles can inherit attributes from their IdP, if the IdP has some. Otherwise,
38
 * one can set attribute in the Profile directly. If there is a conflict between
39
 * IdP-wide and Profile-wide attributes, the more specific ones (i.e. Profile) win.
40
 *
41
 * @author Stefan Winter <[email protected]>
42
 * @author Tomasz Wolniewicz <[email protected]>
43
 *
44
 * @license see LICENSE file in root directory
45
 *
46
 * @package Developer
47
 */
48
class Profile extends EntityWithDBProperties {
49
50
    /**
51
     * This array holds the supported EAP types (in "array" OUTER/INNER representation). They are not synced against the DB after instantiation.
52
     * 
53
     * @var array
54
     */
55
    private $privEaptypes;
56
57
    /**
58
     * This array holds all attributes which are defined on device level only
59
     * 
60
     * @var array
61
     */
62
    private $deviceLevelAttributes;
63
64
    /**
65
     * This array holds all attributes which are defined on device level only
66
     * 
67
     * @var array
68
     */
69
    private $eapLevelAttributes;
70
71
    /**
72
     * Class constructor for existing profiles (use IdP::newProfile() to actually create one). Retrieves all attributes and 
73
     * supported EAP types from the DB and stores them in the priv_ arrays.
74
     * 
75
     * @param int $profileId identifier of the profile in the DB
76
     * @param IdP $idpObject optionally, the institution to which this Profile belongs. Saves the construction of the IdP instance. If omitted, an extra query and instantiation is executed to find out.
77
     */
78
    public function __construct($profileId, $idpObject = 0) {
79
        debug(3, "--- BEGIN Constructing new Profile object ... ---\n");
80
81
        $this->databaseType = "INST";
82
        $this->entityOptionTable = "profile_option";
83
        $this->entityIdColumn = "profile_id";
84
        $this->identifier = $profileId;
85
        $this->attributes = [];
86
87
        $profile = DBConnection::exec($this->databaseType, "SELECT inst_id, realm, use_anon_outer, checkuser_outer, checkuser_value, verify_userinput_suffix as verify, hint_userinput_suffix as hint FROM profile WHERE profile_id = $profileId");
88
        debug(4, $profile);
89
        if (!$profile || $profile->num_rows == 0) {
90
            debug(2, "Profile $profileId not found in database!\n");
91
            throw new Exception("Profile $profileId not found in database!");
92
        }
93
        $profileQuery = mysqli_fetch_object($profile);
94
        if (!($idpObject instanceof IdP)) {
95
            $this->institution = $profileQuery->inst_id;
96
            $idp = new IdP($this->institution);
97
        } else {
98
            $idp = $idpObject;
99
            $this->institution = $idp->name;
100
        }
101
102
        $optioninstance = Options::instance();
103
104
        $this->realm = $profileQuery->realm;
105
        $this->use_anon_outer = $profileQuery->use_anon_outer;
106
        $this->langIndex = CAT::get_lang();
107
        $this->inst_name = $idp->name;
108
109
        $this->checkuser_outer = $profileQuery->checkuser_outer;
110
        $this->checkuser_value = $profileQuery->checkuser_value;
111
        $this->verify = $profileQuery->verify;
112
        $this->hint = $profileQuery->hint;
113
114
        // fetch all atributes from this profile from DB
115
116
        $this->deviceLevelAttributes = $this->fetchDeviceOrEAPLevelAttributes("DEVICES");
117
118
        $this->eapLevelAttributes = $this->fetchDeviceOrEAPLevelAttributes("EAPMETHODS");
119
120
        $tempArrayProfileLevelOnly = $this->retrieveOptionsFromDatabase("SELECT DISTINCT option_name,option_value, row 
121
                                            FROM $this->entityOptionTable
122
                                            WHERE $this->entityIdColumn = $this->identifier  
123
                                            AND device_id = NULL AND eap_method_id = 0
124
                                            ORDER BY option_name", "Profile");
125
126
        // add internal attributes
127
        // they share many attribute properties, so condense the generation
128
129
        $localValueIfAny = (preg_match('/@/', $this->realm) ? substr($this->realm, 0, strpos($this->realm, '@')) : "anonymous" );
130
131
        $internalAttributes = [
132
            "internal:profile_count" => $idp->profileCount(),
133
            "internal:checkuser_outer" => $this->checkuser_outer,
134
            "internal:checkuser_value" => $this->checkuser_value,
135
            "internal:verify_userinput_suffix" => $this->verify,
136
            "internal:hint_userinput_suffix" => $this->hint,
137
            "internal:realm" => preg_replace('/^.*@/', '', $this->realm),
138
            "internal:use_anon_outer" => $this->use_anon_outer,
139
            "internal:anon_local_value" => $localValueIfAny,
140
        ];
141
142
        foreach ($internalAttributes as $attName => $attValue) {
143
            $tempArrayProfileLevelOnly[] = ["name" => $attName,
144
                "value" => $attValue,
145
                "level" => "Profile",
146
                "row" => 0,
147
                "flag" => NULL,
148
                "device" => NULL,
149
                "eapmethod" => 0];
150
        }
151
152
        // now, fetch IdP-wide attributes
153
154
        $idpoptions = $idp->getAttributes();
155
156
        foreach ($idpoptions as $theAttr) {
157
            $temparray[] = [
158
                "name" => $theAttr["name"],
159
                "value" => $theAttr["value"],
160
                "level" => $theAttr["level"],
161
                "row" => $theAttr["row"],
162
                "flag" => $theAttr["flag"],
163
                "device" => NULL,
164
                "eapmethod" => 0,
165
            ];
166
        }
167
168
        // add all attributes which are device or eap method specific to 
169
        // final attribute array (they cannot be overridden)
170
        $this->attributes = array_merge($this->deviceLevelAttributes, $this->eapLevelAttributes);
171
172
        // now add profile-level attributes if not already set on deeper level
173
174
        foreach ($tempArrayProfileLevelOnly as $attrib) {
175
            $ignore = "";
176
            foreach ($this->attributes as $approvedAttrib) {
177
                if ($attrib["name"] == $approvedAttrib["name"] && $approvedAttrib["level"] != "Profile") {
178
                    $ignore = "YES";
179
                }
180
            }
181
            if ($ignore != "YES") {
182
                $this->attributes[] = $attrib;
183
            }
184
        }
185
186
        // now, add IdP-wide attribs
187
188
        foreach ($idpoptions as $attrib) {
189
            $ignore = "";
190
            foreach ($this->attributes as $approvedAttrib) {
191
                if ($attrib["name"] == $approvedAttrib["name"] && $approvedAttrib["level"] != "IdP") {
192
                    $ignore = "YES";
193
                }
194
            }
195
            if ($ignore != "YES") {
196
                $this->attributes[] = $attrib;
197
            }
198
        }
199
200
        $this->name = getLocalisedValue($this->getAttributes('profile:name'), $this->langIndex); // cannot be set per device or eap type
201
202
        $eapMethod = DBConnection::exec($this->databaseType, "SELECT eap_method_id 
203
                                                        FROM supported_eap supp 
204
                                                        WHERE supp.profile_id = $this->identifier 
205
                                                        ORDER by preference");
206
        $eapTypeArray = [];
207
        while ($eapQuery = (mysqli_fetch_object($eapMethod))) {
208
            $eaptype = EAP::EAPMethodArrayFromId($eapQuery->eap_method_id);
209
            $eapTypeArray[] = $eaptype;
210
        }
211
        debug(4, "Looks like this profile supports the following EAP types: ");
212
        debug(4, $eapTypeArray);
213
        $this->privEaptypes = $eapTypeArray;
214
215
        debug(3, "--- END Constructing new Profile object ... ---\n");
216
    }
217
218
    private function fetchDeviceOrEAPLevelAttributes($devicesOrEAPMethods) {
219
        // only one of the two is allowed to be set
220
        $temparray = [];
221
        $optioninstance = Options::instance();
222
        switch ($devicesOrEAPMethods) {
223
            case "DEVICES":
224
                $queryPart = "device_id";
225
                $conditionPart = "AND eap_method_id = 0";
226
                break;
227
            case "EAPMETHODS":
228
                $queryPart = "eap_method_id";
229
                $conditionPart = "AND device_id = NULL";
230
                break;
231
        }
232
233
        $allAttributes = DBConnection::exec($this->databaseType, "SELECT option_name, option_value, $queryPart as deviceormethod, row 
234
                FROM $this->entityOptionTable
235
                WHERE $this->entityIdColumn = $this->identifier $conditionPart");
236
237
        while ($attributeQuery = mysqli_fetch_object($allAttributes)) {
238
239
            $optinfo = $optioninstance->optionType($attributeQuery->option_name);
240
            if ($optinfo['type'] != "file") {
241
                $temparray[] = [
242
                    "name" => $attributeQuery->option_name,
243
                    "value" => $attributeQuery->option_value,
244
                    "level" => "Method",
245
                    "row" => $attributeQuery->row,
246
                    "flag" => $optinfo['flag'],
247
                    "device" => ($devicesOrEAPMethods == "DEVICES" ? $attributeQuery->deviceormethod : NULL),
248
                    "eapmethod" => ($devicesOrEAPMethods == "DEVICES" ? 0 : EAP::EAPMethodArrayFromId($attributeQuery->deviceormethod))];
249
            } else {
250
                $decodedAttribute = $this->decodeFileAttribute($attributeQuery->option_value);
251
252
                $temparray[] = [
253
                    "name" => $attributeQuery->option_name,
254
                    "value" => ( $decodedAttribute['lang'] == "" ? $decodedAttribute['content'] : serialize($decodedAttribute)),
255
                    "level" => "Method",
256
                    "row" => $attributeQuery->row,
257
                    "flag" => $optinfo['flag'],
258
                    "device" => ($devicesOrEAPMethods == "DEVICES" ? $attributeQuery->deviceormethod : NULL),
259
                    "eapmethod" => ($devicesOrEAPMethods == "DEVICES" ? 0 : EAP::EAPMethodArrayFromId($attributeQuery->deviceormethod))];
260
            }
261
        }
262
        return $temparray;
263
    }
264
265
    /**
266
     * find a profile, given its realm
267
     */
268
    public static function profileFromRealm($realm) {
269
        $execQuery = DBConnection::exec($this->databaseType, "SELECT profile_id FROM profile WHERE realm LIKE '%@$realm'");
270
        if ($profileIdQuery = mysqli_fetch_object($execQuery)) {
271
            return $profileIdQuery->profile_id;
272
        }
273
        return FALSE;
274
    }
275
276
    /**
277
     * update the last_changed timestamp for this profile
278
     */
279
    public function updateFreshness() {
280
        DBConnection::exec($this->databaseType, "UPDATE profile SET last_change = CURRENT_TIMESTAMP WHERE profile_id = $this->identifier");
281
    }
282
283
    /**
284
     * gets the last-modified timestamp (useful for caching "dirty" check)
285
     */
286
    public function getFreshness() {
287
        $execUpdate = DBConnection::exec($this->databaseType, "SELECT last_change FROM profile WHERE profile_id = $this->identifier");
288
        if ($freshnessQuery = mysqli_fetch_object($execUpdate)) {
289
            return $freshnessQuery->last_change;
290
        }
291
    }
292
293
    /**
294
     * tests if the configurator needs to be regenerated
295
     * returns the configurator path or NULL if regeneration is required
296
     */
297
298
    /**
299
     * This function tests if the configurator needs to be regenerated (properties of the Profile may have changed since the last configurator generation).
300
     * 
301
     * @param string $device device ID to check
302
     * @return mixed a string with the path to the configurator download, or NULL if it needs to be regenerated
303
     */
304
    public function testCache($device) {
305
        $returnValue = NULL;
306
        $escapedDevice = DBConnection::escape_value($this->databaseType, $device);
307
        $result = DBConnection::exec($this->databaseType, "SELECT download_path, mime, UNIX_TIMESTAMP(installer_time) AS tm FROM downloads WHERE profile_id = $this->identifier AND device_id = '$escapedDevice' AND lang = '$this->langIndex'");
308
        if ($result && $cache = mysqli_fetch_object($result)) {
309
            $execUpdate = DBConnection::exec($this->databaseType, "SELECT UNIX_TIMESTAMP(last_change) AS last_change FROM profile WHERE profile_id = $this->identifier");
310
            if ($lastChange = mysqli_fetch_object($execUpdate)->last_change) {
311
                if ($lastChange < $cache->tm) {
312
                    debug(4, "Installer cached:$cache->download_path\n");
313
                    $returnValue = ['cache' => $cache->download_path, 'mime' => $cache->mime];
314
                }
315
            }
316
        }
317
        return $returnValue;
318
    }
319
320
    /**
321
     * Updates database with new installler location
322
     * 
323
     * @param string device the device identifier string
324
     * @param string path the path where the new installer can be found
325
     */
326
    public function updateCache($device, $path, $mime) {
327
        $escapedDevice = DBConnection::escape_value($this->databaseType, $device);
328
        $escapedPath = DBConnection::escape_value($this->databaseType, $path);
329
        DBConnection::exec($this->databaseType, "INSERT INTO downloads (profile_id,device_id,download_path,mime,lang,installer_time) 
330
                                        VALUES ($this->identifier, '$escapedDevice', '$escapedPath', '$mime', '$this->langIndex', CURRENT_TIMESTAMP ) 
331
                                        ON DUPLICATE KEY UPDATE download_path = '$escapedPath', mime = '$mime', installer_time = CURRENT_TIMESTAMP");
332
    }
333
334
    /**
335
     * Log a new download for our stats
336
     * 
337
     * @param device the device id string
338
     * @param area either admin or user
339
     * @return TRUE if incrementing worked, FALSE if not
340
     */
341
    public function incrementDownloadStats($device, $area) {
342
        $escapedDevice = DBConnection::escape_value($this->databaseType, $device);
343
        if ($area == "admin" || $area == "user") {
344
            DBConnection::exec($this->databaseType, "INSERT INTO downloads (profile_id, device_id, lang, downloads_$area) VALUES ($this->identifier, '$escapedDevice','$this->langIndex', 1) ON DUPLICATE KEY UPDATE downloads_$area = downloads_$area + 1");
345
            return TRUE;
346
        }
347
        return FALSE;
348
    }
349
350
    /**
351
     * Retrieve current download stats from database, either for one specific device or for all devices
352
     * @param string $device the device id string
353
     * @return mixed user downloads of this profile; if device is given, returns the counter as int, otherwise an array with devicename => counter
354
     */
355
    public function getUserDownloadStats($device = 0) {
356
        $returnarray = [];
357
        $numbers = DBConnection::exec($this->databaseType, "SELECT device_id, SUM(downloads_user) AS downloads_user FROM downloads WHERE profile_id = $this->identifier GROUP BY device_id");
358
        while ($statsQuery = mysqli_fetch_object($numbers)) {
359
            $returnarray[$statsQuery->device_id] = $statsQuery->downloads_user;
360
        }
361
        if ($device !== 0) {
362
            if (isset($returnarray[$device])) {
363
                return $returnarray[$device];
364
            }
365
            return 0;
366
        }
367
        // we should pretty-print the device names
368
        $finalarray = [];
369
        $devlist = Devices::listDevices();
370
        foreach ($returnarray as $devId => $count) {
371
            if (isset($devlist[$devId])) {
372
                $finalarray[$devlist[$devId]['display']] = $count;
373
            }
374
        }
375
        return $finalarray;
376
    }
377
378
    /**
379
     * adds an attribute to this profile; not the usual function from EntityWithDBProperties
380
     * because this class also has per-EAP-type and per-device sub-settings
381
     *
382
     * @param string $attrName name of the attribute to set
383
     * @param string $attrValue value of the attribute to set
384
     * @param int $eapType identifier of the EAP type in the database. 0 if the attribute is valid for all EAP types.
385
     * @param string $device identifier of the device in the databse. Omit the argument if attribute is valid for all devices.
386
     */
387
    private function addAttributeAllLevels($attrName, $attrValue, $eapType, $device = 0) {
388
        $escapedAttrName = DBConnection::escape_value($this->databaseType, $attrName);
389
        $escapedAttrValue = DBConnection::escape_value($this->databaseType, $attrValue);
390
391
        DBConnection::exec($this->databaseType, "INSERT INTO $this->entityOptionTable ($this->entityIdColumn, option_name, option_value, eap_method_id" . ($device !== 0 ? ",device_id" : "") . ") 
392
                          VALUES(" . $this->identifier . ", '$escapedAttrName', '$escapedAttrValue', $eapType" . ($device !== 0 ? ",'" . DBConnection::escape_value($this->databaseType, $device) . "'" : "" ) . ")");
393
        $this->updateFreshness();
394
    }
395
    
396
    public function addAttributeEAPSpecific($attrName, $attrValue, $eapType) {
397
        $this->addAttributeAllLevels($attrName, $attrValue, $eapType, 0);
398
    }
399
    
400
    public function addAttributeDeviceSpecific($attrName, $attrValue, $device) {
401
        $this->addAttributeAllLevels($attrName, $attrValue, 0, $device);
402
    }
403
404
    public function addAttribute($attrName, $attrValue) {
405
        $this->addAttributeAllLevels($attrName, $attrValue, 0, 0);
406
    }
407
    
408
    /**
409
     * register new supported EAP method for this profile
410
     *
411
     * @param array $type The EAP Type, as defined in class EAP
412
     * @param int $preference preference of this EAP Type. If a preference value is re-used, the order of EAP types of the same preference level is undefined.
413
     *
414
     */
415
    public function addSupportedEapMethod($type, $preference) {
416
        DBConnection::exec($this->databaseType, "INSERT INTO supported_eap (profile_id, eap_method_id, preference) VALUES ("
417
                . $this->identifier . ", "
418
                . EAP::EAPMethodIdFromArray($type) . ", "
419
                . $preference . ")");
420
        $this->updateFreshness();
421
    }
422
423
    /**
424
     * Deletes the profile from database and uninstantiates itself.
425
     *
426
     */
427
    public function destroy() {
428
        DBConnection::exec($this->databaseType, "DELETE FROM profile_option WHERE profile_id = $this->identifier");
429
        DBConnection::exec($this->databaseType, "DELETE FROM supported_eap WHERE profile_id = $this->identifier");
430
        DBConnection::exec($this->databaseType, "DELETE FROM profile WHERE profile_id = $this->identifier");
431
        unset($this);
432
    }
433
434
    /**
435
     * Removes all supported EAP methods
436
     */
437
    public function flushSupportedEapMethods() {
438
        DBConnection::exec($this->databaseType, "DELETE FROM supported_eap WHERE profile_id = $this->identifier");
439
        $this->updateFreshness();
440
    }
441
442
    /** Toggle anonymous outer ID support.
443
     *
444
     * @param boolean $shallwe TRUE to enable outer identities (needs valid $realm), FALSE to disable
445
     *
446
     */
447
    public function setAnonymousIDSupport($shallwe) {
448
        DBConnection::exec($this->databaseType, "UPDATE profile SET use_anon_outer = " . ($shallwe == true ? "1" : "0") . " WHERE profile_id = $this->identifier");
449
    }
450
451
    /** Toggle special username for realm checks
452
     *
453
     * @param boolean $shallwe TRUE to enable outer identities (needs valid $realm), FALSE to disable
454
     * @param string $localpart the username
455
     *
456
     */
457
    public function setRealmCheckUser($shallwe, $localpart = NULL) {
458
        DBConnection::exec($this->databaseType, "UPDATE profile SET checkuser_outer = " . ($shallwe == true ? "1" : "0") .
459
                ( $localpart !== NULL ? ", checkuser_value = '$localpart' " : "") .
460
                " WHERE profile_id = $this->identifier");
461
    }
462
463
    /** should username be verified or even prefilled?
464
     * 
465
     */
466
    public function setInputVerificationPreference($verify, $hint) {
467
        DBConnection::exec($this->databaseType, "UPDATE profile SET verify_userinput_suffix = " . ($verify == true ? "1" : "0") .
468
                ", hint_userinput_suffix = " . ($hint == true ? "1" : "0") .
469
                " WHERE profile_id = $this->identifier");
470
    }
471
472
    /**
473
     * Specifies the realm of this profile.
474
     * 
475
     * @param string $realm the realm (potentially with the local@ part that should be used for anonymous identities)
476
     */
477
    public function setRealm($realm) {
478
        $escapedRealm = DBConnection::escape_value($this->databaseType, $realm);
479
        DBConnection::exec($this->databaseType, "UPDATE profile SET realm = '$escapedRealm' WHERE profile_id = $this->identifier");
480
        $this->realm = $escapedRealm;
481
    }
482
483
    /**
484
     * Produces an array of EAP methods supported by this profile, ordered by preference
485
     * 
486
     * @param int $completeOnly if set and non-zero limits the output to methods with complete information
487
     * @return array list of EAP methods, (in "array" OUTER/INNER representation)
488
     */
489
    public function getEapMethodsinOrderOfPreference($completeOnly = 0) {
490
        $temparray = [];
491
492
        if ($completeOnly == 0) {
493
            return $this->privEaptypes;
494
        } else {
495
            foreach ($this->privEaptypes as $type) {
496
                if ($this->isEapTypeDefinitionComplete($type) === true) {
497
                    $temparray[] = $type;
498
                }
499
            }
500
            return($temparray);
501
        }
502
    }
503
504
    /**
505
     * Performs a sanity check for a given EAP type - did the admin submit enough information to create installers for him?
506
     * 
507
     * @param array $eaptype the EAP type in "array" OUTER/INNER representation
508
     * @return mixed TRUE if the EAP type is complete; an array of missing attribues if it's incomplete; FALSE if it's incomplete for other reasons
509
     */
510
    public function isEapTypeDefinitionComplete($eaptype) {
511
        $missing = [];
512
        // TLS, TTLS, PEAP outer phase need a CA certficate and a Server Name
513
        if ($eaptype["OUTER"] == PEAP || $eaptype["OUTER"] == TLS || $eaptype["OUTER"] == TTLS || $eaptype["OUTER"] == FAST) {
514
515
            $cnOption = $this->getAttributes("eap:server_name"); // cannot be set per device or eap type
516
            $caOption = $this->getAttributes("eap:ca_file"); // cannot be set per device or eap type
517
518
            if (count($caOption) > 0 && count($cnOption) > 0) {// see if we have at least one root CA cert
519
                foreach ($caOption as $oneCa) {
520
                    $x509 = new X509();
521
                    $caParsed = $x509->processCertificate($oneCa['value']);
522
                    if ($caParsed['root'] == 1) {
523
                        return true;
524
                    }
525
                }
526
                $missing[] = "eap:ca_file";
527
            }
528
            if (count($caOption) == 0) {
529
                $missing[] = "eap:ca_file";
530
            }
531
            if (count($cnOption) == 0) {
532
                $missing[] = "eap:server_name";
533
            }
534
            return $missing;
535
        } elseif ($eaptype["OUTER"] == PWD || $eaptype["INNER"] == NE_SILVERBULLET) {
536
            /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% 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...
537
              $cn_option = $this->getAttributes("eap:server_name", $eaptype);
538
              if (count($cn_option) > 0) */
539
            return true;
540
            /* $missing[] = "eap:server_name";
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% 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...
541
              return $missing; */
542
        }
543
544
        // we have no idea; let's say false
545
546
        return false;
547
    }
548
549
    /**
550
     * list all devices marking their availabiblity and possible redirects
551
     *
552
     * @param string $locale for text-based attributes, either returns values for the default value, or if specified here, in the locale specified
553
     * @return array of device ids display names and their status
554
     */
555
    public function listDevices($locale = 0) {
556
        if ($locale == 0) {
557
            $locale = $this->langIndex;
558
        }
559
        $redirectUrl = 0;
560
        $returnarray = [];
561
        $redirect = $this->getAttributes("device-specific:redirect"); // this might return per-device ones or the general one
562
        // if it was a general one, we are done. Find out if there is one such
563
        // which has device = NULL
564
        $generalRedirect = NULL;
565
        foreach ($redirect as $index => $oneRedirect) {
566
            if ($oneRedirect["level"] == "Profile") {
567
                $generalRedirect = $index;
568
            }
569
        }
570
        if ($generalRedirect !== NULL) { // could be index 0
571
            $unserialised = unserialize($redirect[$generalRedirect]['value']);
572
            return [['id' => '0', 'redirect' => $unserialised['content']]];
573
        }
574
        $preferredEap = $this->getEapMethodsinOrderOfPreference(1);
575
        $eAPOptions = [];
576
        foreach (Devices::listDevices() as $deviceIndex => $deviceProperties) {
577
            $factory = new DeviceFactory($deviceIndex);
578
            $dev = $factory->device;
579
            // find the attribute pertaining to the specific device
580
            $redirectUrl = 0;
581
            foreach ($redirect as $index => $oneRedirect) {
582
                if ($oneRedirect["device"] == $deviceIndex) {
583
                    $redirectUrl = getLocalisedValue($oneRedirect, $locale);
584
                }
585
            }
586
            $devStatus = AVAILABLE;
587
            $message = 0;
588
            if (isset($deviceProperties['options']) && isset($deviceProperties['options']['message']) && $deviceProperties['options']['message']) {
589
                $message = $deviceProperties['options']['message'];
590
            }
591
592
            if ($redirectUrl === 0) {
593
                $eapCustomtext = "";
594
                $deviceCustomtext = "";
595
                if (isset($deviceProperties['options']) && isset($deviceProperties['options']['redirect']) && $deviceProperties['options']['redirect']) {
596
                    $devStatus = HIDDEN;
597
                } else {
598
                    $eap = $dev->getPreferredEapType($preferredEap);
599
                    if ($eap) {
600
                        if (isset($eAPOptions["eap-specific:customtext"][serialize($eap)])) {
601
                            $eapCustomtext = $eAPOptions["eap-specific:customtext"][serialize($eap)];
602
                        } else {
603
                            // fetch customtexts from method-level attributes
604
                            $eapCustomtext = "";
605
                            $customTextAttributes = [];
606
                            $attributeList = $this->getAttributes("eap-specific:redirect");
607
                            foreach ($attributeList as $oneAttribute) {
608
                                if ($oneAttribute["eapmethod"] == $eap) {
609
                                    $customTextAttributes[] = $oneAttribute;
610
                                }
611
                            }
612
                            if (count($customTextAttributes) > 0) {
613
                                $eapCustomtext = getLocalisedValue($customTextAttributes, $locale);
614
                            }
615
                            $eAPOptions["eap-specific:customtext"][serialize($eap)] = $eapCustomtext;
616
                        }
617
                        // fetch customtexts for device
618
                        $deviceCustomtext = "";
619
                        $customTextAttributes = [];
620
                        $attributeList = $this->getAttributes("device-specific:redirect");
621
                        foreach ($attributeList as $oneAttribute) {
622
                                if ($oneAttribute["device"] == $deviceIndex) {
623
                                    $customTextAttributes[] = $oneAttribute;
624
                                }
625
                            }
626
                        $deviceCustomtext = getLocalisedValue($customTextAttributes, $locale);
627
                    } else {
628
                        $devStatus = UNAVAILABLE;
629
                    }
630
                }
631
            }
632
            $returnarray[] = ['id' => $deviceIndex, 'display' => $deviceProperties['display'], 'status' => $devStatus, 'redirect' => $redirectUrl, 'eap_customtext' => $eapCustomtext, 'device_customtext' => $deviceCustomtext, 'message' => $message, 'options' => $deviceProperties['options']];
633
        }
634
        return $returnarray;
635
    }
636
637
    /**
638
     * prepare profile attributes for device modules
639
     * Gets profile attributes taking into account the most specific level on which they may be defined
640
     * as wel as the chosen language.
641
     * can be called with an optional $eap argument
642
     * 
643
     * @param array $eap if specified, retrieves attributes specific to the given EAP type
644
     * @return array list of attributes in collapsed style (index is the attrib name, value is an array of different values)
645
     */
646
    public function getCollapsedAttributes($eap = 0) {
647
        $attrBefore = $this->getAttributes();
648
        $attr = [];
649
        if ($eap != 0) { // filter out attributes pertaining only to a certain EAP type
650
            foreach ($attrBefore as $index => $attrib) {
651
                if ($attrib['eapmethod'] == $eap || $attrib['eapmethod'] == 0) {
652
                    $attr[] = $attrib;
653
                }
654
            }
655
        }
656
        $temp1 = [];
657
        foreach ($attr as $b) {
658
            $name = $b['name'];
659
            $temp1[] = $name;
660
            $level = $b['level'];
661
//            $S[$l] = $z[$l];
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% 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
            $value = $b['value'];
663
            if (!isset($temp[$name][$level])) {
664
                $temp[$name][$level] = [];
665
            }
666
            if ($b['flag'] == 'ML') {
667
                $v = unserialize($value);
668
                $value = [$v['lang'] => $v['content']];
669
            }
670
            $temp[$name][$level][] = $value;
671
            $flags[$name] = $b['flag'];
672
        }
673
        foreach ($temp1 as $name) {
674
            if ($flags[$name] == 'ML') {
675
                $S = [];
676
                if (isset($temp[$name]['Profile'])) {
677
                    foreach ($temp[$name]['Profile'] as $z) {
678
                        foreach ($z as $l => $w) {
679
                            $S[$l] = $w;
680
                        }
681
                    }
682
                }
683
                if (!$S && isset($temp[$name]['IdP'])) {
684
                    foreach ($temp[$name]['IdP'] as $z) {
685
                        foreach ($z as $l => $w) {
686
                            $S[$l] = $w;
687
                        }
688
                    }
689
                }
690
                $out[$name]['langs'] = $S;
691
<<<<<<< HEAD
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_SL
Loading history...
692
                if (isset($S[$this->lang_index]) || isset($S['C']))
693
                    $out[$name][0] = (isset($S[$this->lang_index])) ? $S[$this->lang_index] : $S['C'];
694
                if (isset($S['en']))
695
                   $out[$name][1] = $S['en'];
696
                elseif(isset($S['C']))
697
                   $out[$name][1] = $S['C'];
698
                else
699
                   $out[$name][1] = $out[$name][0];
700
=======
701
                if (isset($S[$this->langIndex]) || isset($S['C'])) {
702
                    $out[$name][0] = (isset($S[$this->langIndex])) ? $S[$this->langIndex] : $S['C'];
703
                }
704
>>>>>>> cc9aa934406bee74d39aa1556cebec9586f485e6
705
            } else {
706
                if (isset($temp[$name]['Method'])) {
707
                    $out[$name] = $temp[$name]['Method'];
708
                } elseif (isset($temp[$name]['Profile'])) {
709
                    $out[$name] = $temp[$name]['Profile'];
710
                } else {
711
                    $out[$name] = $temp[$name]['IdP'];
712
                }
713
            }
714
        }
715
        return($out);
716
    }
717
718
    /**
719
     * 
720
     */
721
    public function getSufficientConfig() {
722
        $result = DBConnection::exec($this->databaseType, "SELECT sufficient_config FROM profile WHERE profile_id = " . $this->identifier);
723
        $configQuery = mysqli_fetch_row($result);
724
        if ($configQuery[0] == "0") {
725
            return FALSE;
726
        }
727
        return TRUE;
728
    }
729
730
    /**
731
     * Checks if the profile has enough information to have something to show to end users. This does not necessarily mean
732
     * that there's a fully configured EAP type - it is sufficient if a redirect has been set for at least one device.
733
     * 
734
     * @return boolean TRUE if enough information for showtime is set; FALSE if not
735
     */
736
    public function readyForShowtime() {
737
        $properConfig = FALSE;
738
        $attribs = $this->getCollapsedAttributes();
739
        // do we have enough to go live? Check if any of the configured EAP methods is completely configured ...
740
        if (sizeof($this->getEapMethodsinOrderOfPreference(1)) > 0) {
741
            $properConfig = TRUE;
742
        }
743
        // if not, it could still be that general redirect has been set
744
        if (!$properConfig) {
745
            if (isset($attribs['device-specific:redirect'])) {
746
                $properConfig = TRUE;
747
            }
748
            // TODO: or maybe just a per-device redirect? would be good enough...
749
        }
750
        // do we know at least one SSID to configure, or work with wired? If not, it's not ready...
751
        if (!isset($attribs['media:SSID']) &&
752
                !isset($attribs['media:SSID_with_legacy']) &&
753
                (!isset(Config::$CONSORTIUM['ssid']) || count(Config::$CONSORTIUM['ssid']) == 0) &&
754
                !isset($attribs['media:wired'])) {
755
            $properConfig = FALSE;
756
        }
757
        return $properConfig;
758
    }
759
760
    /**
761
     * set the showtime and QR-user attributes if prepShowTime says that there is enough info *and* the admin flagged the profile for showing
762
     */
763
    public function prepShowtime() {
764
        $properConfig = $this->readyForShowtime();
765
        if ($properConfig) {
766
            DBConnection::exec($this->databaseType, "UPDATE profile SET sufficient_config = TRUE WHERE profile_id = " . $this->identifier);
767
        } else {
768
            DBConnection::exec($this->databaseType, "UPDATE profile SET sufficient_config = FALSE WHERE profile_id = " . $this->identifier);
769
        }
770
        $attribs = $this->getCollapsedAttributes();
771
        // if not enough info to go live, set FALSE
772
        // even if enough info is there, admin has the ultimate say: 
773
        //   if he doesn't want to go live, no further checks are needed, set FALSE as well
774
        if (!$properConfig || !isset($attribs['profile:production']) || (isset($attribs['profile:production']) && $attribs['profile:production'][0] != "on")) {
775
            DBConnection::exec($this->databaseType, "UPDATE profile SET showtime = FALSE WHERE profile_id = " . $this->identifier);
776
            return;
777
        }
778
        DBConnection::exec($this->databaseType, "UPDATE profile SET showtime = TRUE WHERE profile_id = " . $this->identifier);
779
    }
780
781
    /**
782
     * Checks if the profile is shown (showable) to end users
783
     * @return boolean TRUE if profile is shown; FALSE if not
784
     */
785
    public function isShowtime() {
786
        $result = DBConnection::exec($this->databaseType, "SELECT showtime FROM profile WHERE profile_id = " . $this->identifier);
787
        $resultRow = mysqli_fetch_row($result);
788
        if ($resultRow[0] == "0") {
789
            return FALSE;
790
        }
791
        return TRUE;
792
    }
793
794
    /**
795
     * current language
796
     * @var string
797
     */
798
    private $langIndex;
799
800
    /**
801
     * DB identifier of the parent institution of this profile
802
     * @var int
803
     */
804
    public $institution;
805
806
    /**
807
     * name of the parent institution of this profile in the current language
808
     * @var string
809
     */
810
    public $inst_name;
811
812
    /**
813
     * realm of this profile (empty string if unset)
814
     * @var string
815
     */
816
    public $realm;
817
818
    /**
819
     * boolean value: should anonymous outer IDs be used or not?
820
     * @var boolean
821
     */
822
    public $use_anon_outer;
823
824
}
825