Test Failed
Push — master ( 83b526...bd639c )
by Stefan
23:38
created

ExternalEduroamDBData::dissectCollapsedContacts()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 10
c 1
b 0
f 1
dl 0
loc 13
rs 9.9332
cc 2
nc 2
nop 1
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 file contains the ExternalEduroamDBData class. It contains methods for
25
 * querying the external database.
26
 *
27
 * @author Stefan Winter <[email protected]>
28
 *
29
 * @package Developer
30
 *
31
 */
32
33
namespace core;
34
35
use \Exception;
0 ignored issues
show
Bug introduced by
The type \Exception was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
36
37
/**
38
 * This class interacts with the external DB to fetch operational data for
39
 * read-only purposes.
40
 * 
41
 * @author Stefan Winter <[email protected]>
42
 *
43
 * @license see LICENSE file in root directory
44
 *
45
 * @package Developer
46
 */
47
class ExternalEduroamDBData extends common\Entity implements ExternalLinkInterface {
48
49
    /**
50
     * List of all service providers. Fetched only once by allServiceProviders()
51
     * and then stored in this property for efficiency
52
     * 
53
     * @var array
54
     */
55
    private $SPList = [];
56
57
    /**
58
     * total number of hotspots, cached here for efficiency
59
     * 
60
     * @var int
61
     */
62
    private $counter = -1;
63
64
    /**
65
     * our handle to the DB
66
     * 
67
     * @var DBConnection
68
     */
69
    private $db;
70
71
    /**
72
     * constructor, gives us access to the DB handle we need for queries
73
     */
74
    public function __construct() {
75
        parent::__construct();
76
        $connHandle = DBConnection::handle("EXTERNAL");
77
        if (!$connHandle instanceof DBConnection) {
78
            throw new Exception("Frontend DB is never an array, always a single DB object.");
79
        }
80
        $this->db = $connHandle;
81
        $this->db->exec("SET NAMES 'latin1'");
82
    }
83
84
    /**
85
     * eduroam DB delivers a string with all name variants mangled in one. Pry
86
     * it apart.
87
     * 
88
     * @param string $nameRaw the string with all name variants coerced into one
89
     * @return array language/name pair
90
     * @throws Exception
91
     */
92
    private function splitNames($nameRaw) {
93
        $variants = explode('#', $nameRaw);
94
        $submatches = [];
95
        $returnArray = [];
96
        foreach ($variants as $oneVariant) {
97
            if ($oneVariant == NULL) {
98
                continue;
99
            }
100
            if (!preg_match('/^(..):\ (.*)/', $oneVariant, $submatches) || !isset($submatches[2])) {
101
                $this->loggerInstance->debug(2, "[$nameRaw] We expect 'xx: bla but found '$oneVariant'.");
102
                continue;
103
            }
104
            $returnArray[$submatches[1]] = $submatches[2];
105
        }
106
        return $returnArray;
107
    }
108
109
    /**
110
     * retrieves the list of all service providers from the eduroam database
111
     * 
112
     * @return array list of providers
113
     */
114
    public function listAllServiceProviders() {
115
        if (count($this->SPList) == 0) {
116
            $query = $this->db->exec("SELECT country, inst_name, sp_location FROM view_active_SP_location_eduroamdb");
117
            while ($iterator = mysqli_fetch_object(/** @scrutinizer ignore-type */ $query)) {
118
                $this->SPList[] = ["country" => $iterator->country, "instnames" => $this->splitNames($iterator->inst_name), "locnames" => $this->splitNames($iterator->sp_location)];
119
            }
120
        }
121
        return $this->SPList;
122
    }
123
124
    public function countAllServiceProviders() {
125
        if ($this->counter > -1) {
126
            return $this->counter;
127
        }
128
129
        $cachedNumber = @file_get_contents(ROOT . "/var/tmp/cachedSPNumber.serialised");
130
        if ($cachedNumber !== FALSE) {
131
            $numberData = unserialize($cachedNumber);
132
            $now = new \DateTime();
133
            $cacheDate = $numberData["timestamp"]; // this is a DateTime object
134
            $diff = $now->diff($cacheDate);
135
            if ($diff->y == 0 && $diff->m == 0 && $diff->d == 0) {
136
                $this->counter = $numberData["number"];
137
                return $this->counter;
138
            }
139
        } else { // data in cache is too old or doesn't exist. We really need to ask the database
140
            $list = $this->listAllServiceProviders();
141
            $this->counter = count($list);
142
            file_put_contents(ROOT . "/var/tmp/cachedSPNumber.serialised", serialize(["number" => $this->counter, "timestamp" => new \DateTime()]));
143
            return $this->counter;
144
        }
145
    }
146
147
    public const TYPE_IDPSP = "3";
148
    public const TYPE_SP = "2";
149
    public const TYPE_IDP = "1";
150
    private const TYPE_MAPPING = [
151
        IdP::TYPE_IDP => ExternalEduroamDBData::TYPE_IDP,
152
        IdP::TYPE_IDPSP => ExternalEduroamDBData::TYPE_IDPSP,
153
        IdP::TYPE_SP => ExternalEduroamDBData::TYPE_SP,
154
    ];
155
156
    /**
157
     * 
158
     * @param string $collapsed the blob with contact info from eduroam DB
159
     */
160
    private function dissectCollapsedContacts($collapsed) {
161
        $contacts = explode('#', $collapsed);
162
        $contactList = [];
163
        foreach ($contacts as $contact) {
164
            $matches = [];
165
            preg_match("/^n: (.*), e: (.*), p: (.*)$/", $contact, $matches);
166
            $contactList[] = [
167
                "name" => $matches[1],
168
                "mail" => $matches[2],
169
                "phone" => $matches[3]
170
            ];
171
        }
172
        return $contactList;
173
    }
174
175
    /**
176
     * retrieves entity information from the eduroam database. Choose whether to get all entities with an SP role, an IdP role, or only those with both roles
177
     * 
178
     * @param string      $tld  the top-level domain from which to fetch the entities
179
     * @param string|NULL $type type of entity to retrieve
180
     * @return array list of entities
181
     */
182
    public function listExternalEntities($tld, $type) {
183
        if ($type === NULL) {
184
            $eduroamDbType = NULL;
185
        } else {
186
            $eduroamDbType = self::TYPE_MAPPING[$type]; // anything
187
        }
188
        $returnarray = [];
189
        $query = "SELECT id_institution AS id, country, inst_realm as realmlist, name AS collapsed_name, contact AS collapsed_contact, type FROM view_active_institution WHERE country = ?";
190
        if ($eduroamDbType !== NULL) {
191
            $query .= " AND ( type = '" . ExternalEduroamDBData::TYPE_IDPSP . "' OR type = '" . $eduroamDbType . "')";
192
        }
193
        $externals = $this->db->exec($query, "s", $tld);
194
        // was a SELECT query, so a resource and not a boolean
195
        while ($externalQuery = mysqli_fetch_object(/** @scrutinizer ignore-type */ $externals)) {
196
            $names = $this->splitNames($externalQuery->collapsed_name);
197
            $thelanguage = $names[$this->languageInstance->getLang()] ?? $names["en"] ?? array_shift($names);
198
            $contacts = $this->dissectCollapsedContacts($externalQuery->collapsed_contact);
199
            $mails = [];
200
            foreach ($contacts as $contact) {
201
                // extracting real names is nice, but the <> notation
202
                // really gets screwed up on POSTs and HTML safety
203
                // so better not do this; use only mail addresses
204
                $mails[] = $contact['mail'];
205
            }
206
            $convertedType = array_search($externalQuery->type, self::TYPE_MAPPING);
207
            $returnarray[] = ["ID" => $externalQuery->id, "name" => $thelanguage, "contactlist" => implode(", ", $mails), "country" => $externalQuery->country, "realmlist" => $externalQuery->realmlist, "type" => $convertedType];
208
        }
209
        usort($returnarray, array($this, "usortInstitution"));
210
        return $returnarray;
211
    }
212
213
    /**
214
     * retrieves entity information from the eduroam database having the given realm in the inst_realm field
215
     * Choose which fields to get or get default
216
     * 
217
     * @param string      $realm  the realm
218
     * @param array       $fields list of fields
219
     * @return array list of entities
220
     */
221
    public function listExternalEntitiesByRealm($realm, $fields = []) {
222
        $returnArray = [];
223
        $defaultFields = ['id_institution', 'country', 'inst_realm', 'name', 'contact', 'type'];
224
        if (empty($fields)) {
225
            $fields = $defaultFields;
226
        }
227
        $forSelect = join(', ', $fields);
228
        //$query = "SELECT $forSelect FROM view_active_institution WHERE inst_realm like ?'";
229
        $query = "SELECT $forSelect FROM view_active_institution WHERE inst_realm like '%$realm%'";
230
        //$externals = $this->db->exec($query, "s", $realm);
231
        $externals = $this->db->exec($query);
232
        // was a SELECT query, so a resource and not a boolean
233
        while ($externalQuery = mysqli_fetch_object(/** @scrutinizer ignore-type */ $externals)) {
234
            $record = [];
235
            foreach ($fields as $field) {
236
                $record[$field] = $externalQuery->$field;
237
            }
238
            $returnArray[] = $record;
239
        }
240
        return $returnArray;
241
    }
242
243
    public function listExternalRealms() {
244
        return $this->listExternalEntitiesByRealm(""); // leaing realm empty gets *all*
245
    }
246
247
    /**
248
     * helper function to sort institutions by their name
249
     * 
250
     * @param array $a an array with institution a's information
251
     * @param array $b an array with institution b's information
252
     * @return int the comparison result
253
     */
254
    private function usortInstitution($a, $b) {
255
        return strcasecmp($a["name"], $b["name"]);
256
    }
257
258
    /**
259
     * get all RADIUS/TLS servers for a given federation, with contacts
260
     * [ hostnames => contacts ]
261
     * (hostnames are comma-separated)
262
     * 
263
     * @return array
264
     */
265
    public function listExternalTlsServersFederation($tld) {
266
        $retval = [];
267
        // this includes servers of type "staging", which is fine
268
        $query = "SELECT servers, contacts FROM eduroamv2.view_tls_ro WHERE country = ? AND servers IS NOT NULL AND contacts IS NOT NULL";
269
        $roTldServerTransaction = $this->db->exec($query, "s", $tld);
270
        while ($roServerResponses = mysqli_fetch_object(/** @scrutinizer ignore-type */ $roTldServerTransaction)) {
271
            // there is only one row_id
272
            $retval[$roServerResponses->servers] = $this->dissectCollapsedContacts($roServerResponses->contacts);
273
        }
274
        return $retval;
275
    }
276
277
    /**
278
     * get all RADIUS/TLS servers for all institutions within a given federation
279
     * including their contact details
280
     * 
281
     * "ROid-instid" => [type, inst_name, servers, contacts]
282
     * 
283
     * (hostnames are comma-separated)
284
     * 
285
     * @return array
286
     */
287
    public function listExternalTlsServersInstitution($tld) {
288
        $retval = [];
289
        // this includes servers of type "staging", which is fine
290
        $query = "SELECT ROid, instid, type, inst_name, servers, contacts FROM eduroamv2.view_tls_inst WHERE country = ? AND servers IS NOT NULL AND contacts IS NOT NULL";
291
        $instServerTransaction = $this->db->exec($query, "s", $tld);
292
        while ($instServerResponses = mysqli_fetch_object(/** @scrutinizer ignore-type */ $instServerTransaction)) {
293
            $contactList = $this->dissectCollapsedContacts($instServerResponses->contacts);
294
            $retval[$instServerResponses->ROid . "-". $instServerResponses->instid] = [
295
                "names" => $this->splitNames($instServerResponses->inst_name),
296
                "type" => array_search($instServerResponses->type, self::TYPE_MAPPING),
297
                "servers" => $instServerResponses->servers,
298
                "contacts" => $contactList];
299
        }
300
        return $retval;
301
    }
302
303
}
304