|
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 Federation, IdP and Profile classes. |
|
12
|
|
|
* These should be split into separate files later. |
|
13
|
|
|
* |
|
14
|
|
|
* @package Developer |
|
15
|
|
|
*/ |
|
16
|
|
|
/** |
|
17
|
|
|
* |
|
18
|
|
|
*/ |
|
19
|
|
|
require_once('Helper.php'); |
|
20
|
|
|
require_once('Profile.php'); |
|
21
|
|
|
require_once("CAT.php"); |
|
22
|
|
|
require_once("Options.php"); |
|
23
|
|
|
require_once("DBConnection.php"); |
|
24
|
|
|
require_once("RADIUSTests.php"); |
|
25
|
|
|
require_once('EntityWithDBProperties.php'); |
|
26
|
|
|
|
|
27
|
|
|
define("EXTERNAL_DB_SYNCSTATE_NOT_SYNCED", 0); |
|
28
|
|
|
define("EXTERNAL_DB_SYNCSTATE_SYNCED", 1); |
|
29
|
|
|
define("EXTERNAL_DB_SYNCSTATE_NOTSUBJECTTOSYNCING", 2); |
|
30
|
|
|
|
|
31
|
|
|
/** |
|
32
|
|
|
* This class represents an Identity Provider (IdP). |
|
33
|
|
|
* IdPs have properties of their own, and may have one or more Profiles. The |
|
34
|
|
|
* profiles can override the institution-wide properties. |
|
35
|
|
|
* |
|
36
|
|
|
* @author Stefan Winter <[email protected]> |
|
37
|
|
|
* @author Tomasz Wolniewicz <[email protected]> |
|
38
|
|
|
* |
|
39
|
|
|
* @license see LICENSE file in root directory |
|
40
|
|
|
* |
|
41
|
|
|
* @package Developer |
|
42
|
|
|
*/ |
|
43
|
|
|
class IdP extends EntityWithDBProperties { |
|
44
|
|
|
|
|
45
|
|
|
/** |
|
46
|
|
|
* |
|
47
|
|
|
* @var int synchronisation state with external database, if any |
|
48
|
|
|
*/ |
|
49
|
|
|
private $externalDbSyncstate; |
|
50
|
|
|
|
|
51
|
|
|
/** |
|
52
|
|
|
* The shortname of this IdP's federation |
|
53
|
|
|
* @var string |
|
54
|
|
|
*/ |
|
55
|
|
|
public $federation; |
|
56
|
|
|
|
|
57
|
|
|
/** |
|
58
|
|
|
* Constructs an IdP object based on its details in the database. |
|
59
|
|
|
* Cannot be used to define a new IdP in the database! This happens via Federation::newIdP() |
|
60
|
|
|
* |
|
61
|
|
|
* @param integer $instId the database row identifier |
|
62
|
|
|
*/ |
|
63
|
|
|
public function __construct($instId) { |
|
64
|
|
|
debug(3, "--- BEGIN Constructing new IdP object ... ---\n"); |
|
65
|
|
|
|
|
66
|
|
|
$this->databaseType = "INST"; |
|
67
|
|
|
$this->entityOptionTable = "institution_option"; |
|
68
|
|
|
$this->entityIdColumn = "inst_id"; |
|
69
|
|
|
$this->identifier = $instId; |
|
70
|
|
|
$this->attributes = []; |
|
71
|
|
|
|
|
72
|
|
|
$idp = DBConnection::exec($this->databaseType, "SELECT inst_id, country,external_db_syncstate FROM institution WHERE inst_id = $this->identifier"); |
|
73
|
|
|
if (!$attributeQuery = mysqli_fetch_object($idp)) { |
|
74
|
|
|
throw new Exception("IdP $this->identifier not found in database!"); |
|
75
|
|
|
} |
|
76
|
|
|
|
|
77
|
|
|
$this->federation = $attributeQuery->country; |
|
78
|
|
|
|
|
79
|
|
|
$optioninstance = Options::instance(); |
|
80
|
|
|
|
|
81
|
|
|
$this->externalDbSyncstate = $attributeQuery->externalDbSyncstate; |
|
82
|
|
|
// fetch attributes from DB and keep them in priv_attributes |
|
83
|
|
|
|
|
84
|
|
|
$idPAttributes = DBConnection::exec($this->databaseType, "SELECT DISTINCT option_name,option_value, row FROM institution_option |
|
85
|
|
|
WHERE institution_id = $this->identifier ORDER BY option_name"); |
|
86
|
|
|
|
|
87
|
|
View Code Duplication |
while ($attributeQuery = mysqli_fetch_object($idPAttributes)) { |
|
|
|
|
|
|
88
|
|
|
// decode base64 for files (respecting multi-lang) |
|
89
|
|
|
$optinfo = $optioninstance->optionType($attributeQuery->option_name); |
|
90
|
|
|
$flag = $optinfo['flag']; |
|
91
|
|
|
|
|
92
|
|
|
if ($optinfo['type'] != "file") { |
|
93
|
|
|
$this->attributes[] = ["name" => $attributeQuery->option_name, "value" => $attributeQuery->option_value, "level" => "IdP", "row" => $attributeQuery->row, "flag" => $flag]; |
|
94
|
|
|
} else { |
|
95
|
|
|
$decodedAttribute = $this->decodeFileAttribute($attributeQuery->option_value); |
|
96
|
|
|
|
|
97
|
|
|
$this->attributes[] = ["name" => $attributeQuery->option_name, "value" => ($decodedAttribute['lang'] == "" ? $decodedAttribute['content'] : serialize($decodedAttribute)), "level" => "IdP", "row" => $attributeQuery->row, "flag" => $flag]; |
|
98
|
|
|
} |
|
99
|
|
|
} |
|
100
|
|
|
$this->attributes[] = ["name" => "internal:country", |
|
101
|
|
|
"value" => $this->federation, |
|
102
|
|
|
"level" => "IdP", |
|
103
|
|
|
"row" => 0, |
|
104
|
|
|
"flag" => NULL]; |
|
105
|
|
|
|
|
106
|
|
|
$this->name = getLocalisedValue($this->getAttributes('general:instname'), CAT::get_lang()); |
|
107
|
|
|
debug(3, "--- END Constructing new IdP object ... ---\n"); |
|
108
|
|
|
} |
|
109
|
|
|
|
|
110
|
|
|
/** |
|
111
|
|
|
* This function retrieves all registered profiles for this IdP from the database |
|
112
|
|
|
* |
|
113
|
|
|
* @return array List of Profiles of this IdP |
|
114
|
|
|
* @param int $activeOnly if and set to non-zero will |
|
115
|
|
|
* cause listing of only those institutions which have some valid profiles defined. |
|
116
|
|
|
*/ |
|
117
|
|
|
public function listProfiles($activeOnly = 0) { |
|
118
|
|
|
$query = "SELECT profile_id FROM profile WHERE inst_id = $this->identifier" . ($activeOnly ? " AND showtime = 1" : ""); |
|
119
|
|
|
$allProfiles = DBConnection::exec($this->databaseType, $query); |
|
120
|
|
|
$returnarray = []; |
|
121
|
|
|
while ($profileQuery = mysqli_fetch_object($allProfiles)) { |
|
122
|
|
|
$oneProfile = new Profile($profileQuery->profile_id, $this); |
|
|
|
|
|
|
123
|
|
|
$oneProfile->institution = $this->identifier; |
|
|
|
|
|
|
124
|
|
|
$returnarray[] = $oneProfile; |
|
125
|
|
|
} |
|
126
|
|
|
return $returnarray; |
|
127
|
|
|
} |
|
128
|
|
|
|
|
129
|
|
View Code Duplication |
public function isOneProfileConfigured() { |
|
|
|
|
|
|
130
|
|
|
$allProfiles = DBConnection::exec($this->databaseType, "SELECT profile_id FROM profile WHERE inst_id = $this->identifier AND sufficient_config = 1"); |
|
131
|
|
|
if (mysqli_num_rows($allProfiles) > 0) { |
|
132
|
|
|
return TRUE; |
|
133
|
|
|
} |
|
134
|
|
|
return FALSE; |
|
135
|
|
|
} |
|
136
|
|
|
|
|
137
|
|
View Code Duplication |
public function isOneProfileShowtime() { |
|
|
|
|
|
|
138
|
|
|
$allProfiles = DBConnection::exec($this->databaseType, "SELECT profile_id FROM profile WHERE inst_id = $this->identifier AND showtime = 1"); |
|
139
|
|
|
if (mysqli_num_rows($allProfiles) > 0) { |
|
140
|
|
|
return TRUE; |
|
141
|
|
|
} |
|
142
|
|
|
return FALSE; |
|
143
|
|
|
} |
|
144
|
|
|
|
|
145
|
|
|
public function getAllProfileStatusOverview() { |
|
146
|
|
|
$allProfiles = DBConnection::exec($this->databaseType, "SELECT status_dns, status_cert, status_reachability, status_TLS, last_status_check FROM profile WHERE inst_id = $this->identifier AND sufficient_config = 1"); |
|
147
|
|
|
$returnarray = ['dns' => RETVAL_SKIPPED, 'cert' => L_OK, 'reachability' => RETVAL_SKIPPED, 'TLS' => RETVAL_SKIPPED, 'checktime' => NULL]; |
|
148
|
|
|
while ($statusQuery = mysqli_fetch_object($allProfiles)) { |
|
149
|
|
|
if ($statusQuery->status_dns < $returnarray['dns']) { |
|
150
|
|
|
$returnarray['dns'] = $statusQuery->status_dns; |
|
151
|
|
|
} |
|
152
|
|
|
if ($statusQuery->status_reachability < $returnarray['reachability']) { |
|
153
|
|
|
$returnarray['reachability'] = $statusQuery->status_reachability; |
|
154
|
|
|
} |
|
155
|
|
|
if ($statusQuery->status_TLS < $returnarray['TLS']) { |
|
156
|
|
|
$returnarray['TLS'] = $statusQuery->status_TLS; |
|
157
|
|
|
} |
|
158
|
|
|
if ($statusQuery->status_cert < $returnarray['cert']) { |
|
159
|
|
|
$returnarray['cert'] = $statusQuery->status_cert; |
|
160
|
|
|
} |
|
161
|
|
|
if ($statusQuery->last_status_check > $returnarray['checktime']) { |
|
162
|
|
|
$returnarray['checktime'] = $statusQuery->last_status_check; |
|
163
|
|
|
} |
|
164
|
|
|
} |
|
165
|
|
|
return $returnarray; |
|
166
|
|
|
} |
|
167
|
|
|
|
|
168
|
|
|
/** This function retrieves an array of authorised users which can |
|
169
|
|
|
* manipulate this institution. |
|
170
|
|
|
* |
|
171
|
|
|
* @return array owners of the institution; numbered array with members ID, MAIL and LEVEL |
|
172
|
|
|
*/ |
|
173
|
|
|
public function owner() { |
|
174
|
|
|
$returnarray = []; |
|
175
|
|
|
$admins = DBConnection::exec($this->databaseType, "SELECT user_id, orig_mail, blesslevel FROM ownership WHERE institution_id = $this->identifier ORDER BY user_id"); |
|
176
|
|
|
while ($ownerQuery = mysqli_fetch_object($admins)) { |
|
177
|
|
|
$returnarray[] = ['ID' => $ownerQuery->user_id, 'MAIL' => $ownerQuery->orig_mail, 'LEVEL' => $ownerQuery->blesslevel]; |
|
178
|
|
|
} |
|
179
|
|
|
return $returnarray; |
|
180
|
|
|
} |
|
181
|
|
|
|
|
182
|
|
|
/** |
|
183
|
|
|
* This function gets the profile count for a given IdP |
|
184
|
|
|
* The count could be retreived from the listProfiles method |
|
185
|
|
|
* but this is less expensive. |
|
186
|
|
|
* |
|
187
|
|
|
* @return int profile count |
|
188
|
|
|
*/ |
|
189
|
|
|
public function profileCount() { |
|
190
|
|
|
$result = DBConnection::exec($this->databaseType, "SELECT profile_id FROM profile |
|
191
|
|
|
WHERE inst_id = $this->identifier"); |
|
192
|
|
|
return(mysqli_num_rows($result)); |
|
193
|
|
|
} |
|
194
|
|
|
|
|
195
|
|
|
/** |
|
196
|
|
|
* This function sets the timestamp of last modification of the child profiles to the current timestamp. This is needed |
|
197
|
|
|
* for installer caching: all installers which are on disk must be re-created if an attribute changes. This timestamp here |
|
198
|
|
|
* is used to determine if the installer on disk is still new enough. |
|
199
|
|
|
*/ |
|
200
|
|
|
public function updateFreshness() { |
|
201
|
|
|
// freshness is always defined for *Profiles* |
|
202
|
|
|
// IdP needs to update timestamp of all its profiles if an IdP-wide attribute changed |
|
203
|
|
|
DBConnection::exec($this->databaseType, "UPDATE profile SET last_change = CURRENT_TIMESTAMP WHERE inst_id = '$this->identifier'"); |
|
204
|
|
|
} |
|
205
|
|
|
|
|
206
|
|
|
/** |
|
207
|
|
|
* Adds a new profile to this IdP. |
|
208
|
|
|
* Only creates the DB entry for the Profile. If you want to add attributes later, see Profile::addAttribute(). |
|
209
|
|
|
* |
|
210
|
|
|
* @return object new Profile object if successful, or FALSE if an error occured |
|
211
|
|
|
*/ |
|
212
|
|
|
public function newProfile() { |
|
213
|
|
|
DBConnection::exec($this->databaseType, "INSERT INTO profile (inst_id) VALUES($this->identifier)"); |
|
214
|
|
|
$identifier = DBConnection::lastID($this->databaseType); |
|
215
|
|
|
|
|
216
|
|
|
if ($identifier > 0) { |
|
217
|
|
|
return new Profile($identifier, $this); |
|
|
|
|
|
|
218
|
|
|
} |
|
219
|
|
|
return NULL; |
|
220
|
|
|
} |
|
221
|
|
|
|
|
222
|
|
|
/** |
|
223
|
|
|
* deletes the IdP and all its profiles |
|
224
|
|
|
*/ |
|
225
|
|
|
public function destroy() { |
|
226
|
|
|
/* delete all profiles */ |
|
227
|
|
|
foreach ($this->listProfiles() as $profile) { |
|
228
|
|
|
$profile->destroy(); |
|
229
|
|
|
} |
|
230
|
|
|
/* double-check that all profiles are gone */ |
|
231
|
|
|
$profiles = $this->listProfiles(); |
|
232
|
|
|
|
|
233
|
|
|
if (count($profiles) > 0) { |
|
234
|
|
|
die("This IdP shouldn't have any profiles any more!"); |
|
235
|
|
|
} |
|
236
|
|
|
|
|
237
|
|
|
DBConnection::exec($this->databaseType, "DELETE FROM ownership WHERE institution_id = $this->identifier"); |
|
238
|
|
|
DBConnection::exec($this->databaseType, "DELETE FROM institution_option WHERE institution_id = $this->identifier"); |
|
239
|
|
|
DBConnection::exec($this->databaseType, "DELETE FROM institution WHERE inst_id = $this->identifier"); |
|
240
|
|
|
|
|
241
|
|
|
// notify federation admins |
|
242
|
|
|
|
|
243
|
|
|
$fed = new Federation($this->federation); |
|
244
|
|
|
foreach ($fed->listFederationAdmins() as $id) { |
|
245
|
|
|
$user = new User($id); |
|
246
|
|
|
$message = sprintf(_("Hi, |
|
247
|
|
|
|
|
248
|
|
|
the Identity Provider %s in your %s federation %s has been deleted from %s. |
|
249
|
|
|
|
|
250
|
|
|
We thought you might want to know. |
|
251
|
|
|
|
|
252
|
|
|
Best regards, |
|
253
|
|
|
|
|
254
|
|
|
%s"), $this->name, Config::$CONSORTIUM['name'], strtoupper($fed->name), Config::$APPEARANCE['productname'], Config::$APPEARANCE['productname_long']); |
|
255
|
|
|
$user->sendMailToUser(_("IdP in your federation was deleted"), $message); |
|
256
|
|
|
} |
|
257
|
|
|
unset($this); |
|
258
|
|
|
} |
|
259
|
|
|
|
|
260
|
|
|
/** |
|
261
|
|
|
* Performs a lookup in an external database to determine matching entities to this IdP. The business logic of this function is |
|
262
|
|
|
* roaming consortium specific; if no match algorithm is known for the consortium, FALSE is returned. |
|
263
|
|
|
* |
|
264
|
|
|
* @return array list of entities in external database that correspond to this IdP or FALSE if no consortium-specific matching function is defined |
|
265
|
|
|
*/ |
|
266
|
|
|
public function getExternalDBSyncCandidates() { |
|
267
|
|
|
if (Config::$CONSORTIUM['name'] == "eduroam" && isset(Config::$CONSORTIUM['deployment-voodoo']) && Config::$CONSORTIUM['deployment-voodoo'] == "Operations Team") { // SW: APPROVED |
|
268
|
|
|
$list = []; |
|
269
|
|
|
$usedarray = []; |
|
270
|
|
|
// extract all institutions from the country |
|
271
|
|
|
$candidateList = DBConnection::exec("EXTERNAL", "SELECT id_institution AS id, name AS collapsed_name FROM view_active_idp_institution WHERE country = '" . strtolower($this->federation) . "'"); |
|
272
|
|
|
|
|
273
|
|
|
$alreadyUsed = DBConnection::exec($this->databaseType, "SELECT DISTINCT external_db_id FROM institution WHERE external_db_id IS NOT NULL AND external_db_syncstate = " . EXTERNAL_DB_SYNCSTATE_SYNCED); |
|
274
|
|
|
while ($alreadyUsedQuery = mysqli_fetch_object($alreadyUsed)) { |
|
275
|
|
|
$usedarray[] = $alreadyUsedQuery->external_db_id; |
|
276
|
|
|
} |
|
277
|
|
|
|
|
278
|
|
|
// and split them into ID, LANG, NAME pairs |
|
279
|
|
|
while ($candidateListQuery = mysqli_fetch_object($candidateList)) { |
|
280
|
|
|
if (in_array($candidateListQuery->id, $usedarray)) { |
|
281
|
|
|
continue; |
|
282
|
|
|
} |
|
283
|
|
|
$names = explode('#', $candidateListQuery->collapsed_name); |
|
284
|
|
|
foreach ($names as $name) { |
|
285
|
|
|
$perlang = explode(': ', $name, 2); |
|
286
|
|
|
$list[] = ["ID" => $candidateListQuery->id, "lang" => $perlang[0], "name" => $perlang[1]]; |
|
287
|
|
|
} |
|
288
|
|
|
} |
|
289
|
|
|
// now see if any of the languages in CAT match any of those in the external DB |
|
290
|
|
|
$mynames = $this->getAttributes("general:instname"); |
|
291
|
|
|
$matchingCandidates = []; |
|
292
|
|
|
foreach ($mynames as $onename) { |
|
293
|
|
|
foreach ($list as $listentry) { |
|
294
|
|
|
$unserialised = unserialize($onename['value']); |
|
295
|
|
|
if (($unserialised['lang'] == $listentry['lang'] || $unserialised['lang'] == "C") && $unserialised['content'] == $listentry['name']) { |
|
296
|
|
|
if (array_search($listentry['ID'], $matchingCandidates) === FALSE) { |
|
297
|
|
|
$matchingCandidates[] = $listentry['ID']; |
|
298
|
|
|
} |
|
299
|
|
|
} |
|
300
|
|
|
} |
|
301
|
|
|
} |
|
302
|
|
|
return $matchingCandidates; |
|
303
|
|
|
} |
|
304
|
|
|
return FALSE; |
|
305
|
|
|
} |
|
306
|
|
|
|
|
307
|
|
|
public function getExternalDBSyncState() { |
|
308
|
|
|
if (Config::$CONSORTIUM['name'] == "eduroam" && isset(Config::$CONSORTIUM['deployment-voodoo']) && Config::$CONSORTIUM['deployment-voodoo'] == "Operations Team") { // SW: APPROVED |
|
309
|
|
|
return $this->externalDbSyncstate; |
|
310
|
|
|
} |
|
311
|
|
|
return EXTERNAL_DB_SYNCSTATE_NOTSUBJECTTOSYNCING; |
|
312
|
|
|
} |
|
313
|
|
|
|
|
314
|
|
|
/** |
|
315
|
|
|
* Retrieves the external DB identifier of this institution. Returns FALSE if no ID is known. |
|
316
|
|
|
* |
|
317
|
|
|
* @return int the external identifier; or FALSE if no external ID is known |
|
318
|
|
|
*/ |
|
319
|
|
|
public function getExternalDBId() { |
|
320
|
|
|
if (Config::$CONSORTIUM['name'] == "eduroam" && isset(Config::$CONSORTIUM['deployment-voodoo']) && Config::$CONSORTIUM['deployment-voodoo'] == "Operations Team") { // SW: APPROVED |
|
321
|
|
|
$id = DBConnection::exec($this->databaseType, "SELECT external_db_id FROM institution WHERE inst_id = $this->identifier AND external_db_syncstate = " . EXTERNAL_DB_SYNCSTATE_SYNCED); |
|
322
|
|
|
if (mysqli_num_rows($id) == 0) { |
|
323
|
|
|
return FALSE; |
|
|
|
|
|
|
324
|
|
|
} else { |
|
325
|
|
|
$externalIdQuery = mysqli_fetch_object($id); |
|
326
|
|
|
return $externalIdQuery->external_db_id; |
|
327
|
|
|
} |
|
328
|
|
|
} |
|
329
|
|
|
return FALSE; |
|
|
|
|
|
|
330
|
|
|
} |
|
331
|
|
|
|
|
332
|
|
|
/** |
|
333
|
|
|
* Fetches information from the external database about this IdP |
|
334
|
|
|
* |
|
335
|
|
|
* @return array details about that institution. Array may be empty if entity is not synced |
|
336
|
|
|
*/ |
|
337
|
|
|
public function getExternalDBEntityDetails() { |
|
338
|
|
|
$externalId = $this->getExternalDBId(); |
|
339
|
|
|
if ($externalId !== FALSE) { |
|
340
|
|
|
return Federation::getExternalDBEntityDetails($externalId); |
|
341
|
|
|
} |
|
342
|
|
|
return []; |
|
343
|
|
|
} |
|
344
|
|
|
|
|
345
|
|
|
public function setExternalDBId($identifier) { |
|
346
|
|
|
$escapedIdentifier = DBConnection::escape_value($this->databaseType, $identifier); |
|
347
|
|
|
if (Config::$CONSORTIUM['name'] == "eduroam" && isset(Config::$CONSORTIUM['deployment-voodoo']) && Config::$CONSORTIUM['deployment-voodoo'] == "Operations Team") { // SW: APPROVED |
|
348
|
|
|
$alreadyUsed = DBConnection::exec($this->databaseType, "SELECT DISTINCT external_db_id FROM institution WHERE external_db_id = '$escapedIdentifier' AND external_db_syncstate = " . EXTERNAL_DB_SYNCSTATE_SYNCED); |
|
349
|
|
|
|
|
350
|
|
|
if (mysqli_num_rows($alreadyUsed) == 0) { |
|
351
|
|
|
DBConnection::exec($this->databaseType, "UPDATE institution SET external_db_id = '$escapedIdentifier', external_db_syncstate = " . EXTERNAL_DB_SYNCSTATE_SYNCED . " WHERE inst_id = $this->identifier"); |
|
352
|
|
|
} |
|
353
|
|
|
} |
|
354
|
|
|
} |
|
355
|
|
|
} |
|
356
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.