1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* consentAdmin - Consent administration module |
5
|
|
|
* |
6
|
|
|
* This module enables the user to add and remove consents given for a given |
7
|
|
|
* Service Provider. |
8
|
|
|
* |
9
|
|
|
* The module relies on methods and functions from the Consent module and can |
10
|
|
|
* not be user without it. |
11
|
|
|
* |
12
|
|
|
* Author: Mads Freek <[email protected]>, Jacob Christiansen <[email protected]> |
13
|
|
|
*/ |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* Runs the processing chain and ignores all filter which have user |
17
|
|
|
* interaction. |
18
|
|
|
* |
19
|
|
|
* @param array $idp_metadata |
20
|
|
|
* @param string $source |
21
|
|
|
* @param array $sp_metadata |
22
|
|
|
* @param string $sp_entityid |
23
|
|
|
* @param array $attributes |
24
|
|
|
* @param string $userid |
25
|
|
|
* @param bool $hashAttributes |
26
|
|
|
* @param array $excludeAttributes |
27
|
|
|
* @return array |
28
|
|
|
*/ |
29
|
|
|
function driveProcessingChain( |
30
|
|
|
array $idp_metadata, |
31
|
|
|
string $source, |
32
|
|
|
array $sp_metadata, |
33
|
|
|
string $sp_entityid, |
34
|
|
|
array $attributes, |
35
|
|
|
string $userid, |
36
|
|
|
bool $hashAttributes = false, |
37
|
|
|
array $excludeAttributes = [] |
38
|
|
|
): array { |
39
|
|
|
/* |
40
|
|
|
* Create a new processing chain |
41
|
|
|
*/ |
42
|
|
|
$pc = new \SimpleSAML\Auth\ProcessingChain($idp_metadata, $sp_metadata, 'idp'); |
43
|
|
|
|
44
|
|
|
/* |
45
|
|
|
* Construct the state. |
46
|
|
|
* REMEMBER: Do not set Return URL if you are calling processStatePassive |
47
|
|
|
*/ |
48
|
|
|
$authProcState = [ |
49
|
|
|
'Attributes' => $attributes, |
50
|
|
|
'Destination' => $sp_metadata, |
51
|
|
|
'SPMetadata' => $sp_metadata, |
52
|
|
|
'Source' => $idp_metadata, |
53
|
|
|
'IdPMetadata' => $idp_metadata, |
54
|
|
|
'isPassive' => true, |
55
|
|
|
]; |
56
|
|
|
/* we're being bridged, so add that info to the state */ |
57
|
|
|
if (strpos($source, '-idp-remote|') !== false) { |
58
|
|
|
/** @var int $i */ |
59
|
|
|
$i = strpos($source, '|'); |
60
|
|
|
$authProcState['saml:sp:IdP'] = substr($source, $i + 1); |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
/* |
64
|
|
|
* Call processStatePAssive. |
65
|
|
|
* We are not interested in any user interaction, only modifications to the attributes |
66
|
|
|
*/ |
67
|
|
|
$pc->processStatePassive($authProcState); |
68
|
|
|
|
69
|
|
|
$attributes = $authProcState['Attributes']; |
70
|
|
|
// Remove attributes that do not require consent/should be excluded |
71
|
|
|
foreach ($attributes as $attrkey => $attrval) { |
72
|
|
|
if (in_array($attrkey, $excludeAttributes)) { |
73
|
|
|
unset($attributes[$attrkey]); |
74
|
|
|
} |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
/* |
78
|
|
|
* Generate identifiers and hashes |
79
|
|
|
*/ |
80
|
|
|
$destination = $sp_metadata['metadata-set'] . '|' . $sp_entityid; |
81
|
|
|
|
82
|
|
|
$targeted_id = \SimpleSAML\Module\consent\Auth\Process\Consent::getTargetedID($userid, $source, $destination); |
83
|
|
|
$attribute_hash = \SimpleSAML\Module\consent\Auth\Process\Consent::getAttributeHash($attributes, $hashAttributes); |
84
|
|
|
|
85
|
|
|
\SimpleSAML\Logger::info('consentAdmin: user: ' . $userid); |
86
|
|
|
\SimpleSAML\Logger::info('consentAdmin: target: ' . $targeted_id); |
87
|
|
|
\SimpleSAML\Logger::info('consentAdmin: attribute: ' . $attribute_hash); |
88
|
|
|
|
89
|
|
|
// Return values |
90
|
|
|
return [$targeted_id, $attribute_hash, $attributes]; |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
// Get config object |
94
|
|
|
$config = \SimpleSAML\Configuration::getInstance(); |
95
|
|
|
$cA_config = \SimpleSAML\Configuration::getConfig('module_consentAdmin.php'); |
96
|
|
|
$authority = $cA_config->getValue('authority'); |
97
|
|
|
|
98
|
|
|
$as = new \SimpleSAML\Auth\Simple($authority); |
|
|
|
|
99
|
|
|
|
100
|
|
|
// If request is a logout request |
101
|
|
|
if (array_key_exists('logout', $_REQUEST)) { |
102
|
|
|
$returnURL = $cA_config->getValue('returnURL'); |
103
|
|
|
$as->logout($returnURL); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
$hashAttributes = $cA_config->getValue('attributes.hash'); |
107
|
|
|
|
108
|
|
|
$excludeAttributes = $cA_config->getValue('attributes.exclude', []); |
109
|
|
|
|
110
|
|
|
// Check if valid local session exists |
111
|
|
|
$as->requireAuth(); |
112
|
|
|
|
113
|
|
|
// Get released attributes |
114
|
|
|
$attributes = $as->getAttributes(); |
115
|
|
|
|
116
|
|
|
// Get metadata storage handler |
117
|
|
|
$metadata = \SimpleSAML\Metadata\MetaDataStorageHandler::getMetadataHandler(); |
118
|
|
|
|
119
|
|
|
/* |
120
|
|
|
* Get IdP id and metadata |
121
|
|
|
*/ |
122
|
|
|
|
123
|
|
|
$idp_entityid = $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted'); |
124
|
|
|
$idp_metadata = $metadata->getMetaData($idp_entityid, 'saml20-idp-hosted'); |
125
|
|
|
|
126
|
|
|
// Calc correct source |
127
|
|
|
if ($as->getAuthData('saml:sp:IdP') !== null) { |
128
|
|
|
// from a remote idp (as bridge) |
129
|
|
|
$source = 'saml20-idp-remote|' . $as->getAuthData('saml:sp:IdP'); |
130
|
|
|
} else { |
131
|
|
|
// from the local idp |
132
|
|
|
$source = $idp_metadata['metadata-set'] . '|' . $idp_entityid; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
// Get user ID |
136
|
|
|
if (isset($idp_metadata['userid.attribute']) && is_string($idp_metadata['userid.attribute'])) { |
137
|
|
|
$userid_attributename = $idp_metadata['userid.attribute']; |
138
|
|
|
} else { |
139
|
|
|
$userid_attributename = 'eduPersonPrincipalName'; |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
$userids = $attributes[$userid_attributename]; |
143
|
|
|
|
144
|
|
|
if (empty($userids)) { |
145
|
|
|
throw new \Exception('Could not generate useridentifier for storing consent. Attribute [' . |
146
|
|
|
$userid_attributename . '] was not available.'); |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
$userid = $userids[0]; |
150
|
|
|
|
151
|
|
|
// Get all SP metadata |
152
|
|
|
$all_sp_metadata = $metadata->getList('saml20-sp-remote'); |
153
|
|
|
|
154
|
|
|
// Parse action, if any |
155
|
|
|
$action = null; |
156
|
|
|
$sp_entityid = null; |
157
|
|
|
if (!empty($_GET['cv'])) { |
158
|
|
|
$sp_entityid = $_GET['cv']; |
159
|
|
|
} |
160
|
|
|
if (!empty($_GET['action'])) { |
161
|
|
|
$action = $_GET["action"]; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
\SimpleSAML\Logger::critical('consentAdmin: sp: ' . $sp_entityid . ' action: ' . $action); |
165
|
|
|
|
166
|
|
|
// Remove services, whitch have consent disabled |
167
|
|
|
if (isset($idp_metadata['consent.disable'])) { |
168
|
|
|
foreach ($idp_metadata['consent.disable'] as $disable) { |
169
|
|
|
if (array_key_exists($disable, $all_sp_metadata)) { |
170
|
|
|
unset($all_sp_metadata[$disable]); |
171
|
|
|
} |
172
|
|
|
} |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
\SimpleSAML\Logger::info('consentAdmin: ' . $idp_entityid); |
176
|
|
|
|
177
|
|
|
// Parse consent config |
178
|
|
|
$consent_storage = \SimpleSAML\Module\consent\Store::parseStoreConfig($cA_config->getValue('consentadmin')); |
179
|
|
|
|
180
|
|
|
// Calc correct user ID hash |
181
|
|
|
$hashed_user_id = \SimpleSAML\Module\consent\Auth\Process\Consent::getHashedUserID($userid, $source); |
182
|
|
|
|
183
|
|
|
// If a checkbox have been clicked |
184
|
|
|
if ($action !== null && $sp_entityid !== null) { |
185
|
|
|
// init template to enable translation of status messages |
186
|
|
|
$template = new \SimpleSAML\XHTML\Template( |
187
|
|
|
$config, |
188
|
|
|
'consentAdmin:consentadminajax.twig', |
189
|
|
|
'consentAdmin:consentadmin' |
190
|
|
|
); |
191
|
|
|
$translator = $template->getTranslator(); |
192
|
|
|
|
193
|
|
|
// Get SP metadata |
194
|
|
|
$sp_metadata = $metadata->getMetaData($sp_entityid, 'saml20-sp-remote'); |
195
|
|
|
|
196
|
|
|
// Run AuthProc filters |
197
|
|
|
list($targeted_id, $attribute_hash, $attributes_new) = driveProcessingChain( |
198
|
|
|
$idp_metadata, |
199
|
|
|
$source, |
200
|
|
|
$sp_metadata, |
201
|
|
|
$sp_entityid, |
202
|
|
|
$attributes, |
203
|
|
|
$userid, |
204
|
|
|
$hashAttributes, |
|
|
|
|
205
|
|
|
$excludeAttributes |
206
|
|
|
); |
207
|
|
|
|
208
|
|
|
// Add a consent (or update if attributes have changed and old consent for SP and IdP exists) |
209
|
|
|
if ($action == 'true') { |
210
|
|
|
$isStored = $consent_storage->saveConsent($hashed_user_id, $targeted_id, $attribute_hash); |
211
|
|
|
} else { |
212
|
|
|
if ($action == 'false') { |
213
|
|
|
// Got consent, so this is a request to remove it |
214
|
|
|
$rowcount = $consent_storage->deleteConsent($hashed_user_id, $targeted_id); |
215
|
|
|
if ($rowcount > 0) { |
216
|
|
|
$isStored = false; |
217
|
|
|
} else { |
218
|
|
|
throw new \Exception("Unknown action (should not happen)"); |
219
|
|
|
} |
220
|
|
|
} else { |
221
|
|
|
\SimpleSAML\Logger::info('consentAdmin: unknown action'); |
222
|
|
|
$isStored = null; |
223
|
|
|
} |
224
|
|
|
} |
225
|
|
|
$template->data['isStored'] = $isStored; |
226
|
|
|
$template->send(); |
227
|
|
|
exit; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
// Get all consents for user |
231
|
|
|
$user_consent_list = $consent_storage->getConsents($hashed_user_id); |
232
|
|
|
|
233
|
|
|
// Parse list of consents |
234
|
|
|
$user_consent = []; |
235
|
|
|
foreach ($user_consent_list as $c) { |
236
|
|
|
$user_consent[$c[0]] = $c[1]; |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
$template_sp_content = []; |
240
|
|
|
|
241
|
|
|
// Init template |
242
|
|
|
$template = new \SimpleSAML\XHTML\Template($config, 'consentAdmin:consentadmin.twig', 'consentAdmin:consentadmin'); |
243
|
|
|
$translator = $template->getTranslator(); |
244
|
|
|
$translator->includeLanguageFile('attributes'); // attribute listings translated by this dictionary |
245
|
|
|
|
246
|
|
|
$sp_empty_description = $translator->getTag('sp_empty_description'); |
247
|
|
|
$sp_list = []; |
248
|
|
|
|
249
|
|
|
// Process consents for all SP |
250
|
|
|
foreach ($all_sp_metadata as $sp_entityid => $sp_values) { |
251
|
|
|
// Get metadata for SP |
252
|
|
|
$sp_metadata = $metadata->getMetaData($sp_entityid, 'saml20-sp-remote'); |
253
|
|
|
|
254
|
|
|
// Run attribute filters |
255
|
|
|
list($targeted_id, $attribute_hash, $attributes_new) = driveProcessingChain( |
256
|
|
|
$idp_metadata, |
257
|
|
|
$source, |
258
|
|
|
$sp_metadata, |
259
|
|
|
$sp_entityid, |
260
|
|
|
$attributes, |
261
|
|
|
$userid, |
262
|
|
|
$hashAttributes, |
263
|
|
|
$excludeAttributes |
264
|
|
|
); |
265
|
|
|
|
266
|
|
|
// Translate attribute-names |
267
|
|
|
foreach ($attributes_new as $orig_name => $value) { |
268
|
|
|
if (isset($template->data['attribute_' . htmlspecialchars(strtolower($orig_name))])) { |
269
|
|
|
$old_name = $template->data['attribute_' . htmlspecialchars(strtolower($orig_name))]; |
270
|
|
|
} |
271
|
|
|
$name = $translator->getAttributeTranslation(strtolower($orig_name)); // translate |
272
|
|
|
|
273
|
|
|
$attributes_new[$name] = $value; |
274
|
|
|
unset($attributes_new[$orig_name]); |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
// Check if consent exists |
278
|
|
|
if (array_key_exists($targeted_id, $user_consent)) { |
279
|
|
|
$sp_status = "changed"; |
280
|
|
|
\SimpleSAML\Logger::info('consentAdmin: changed'); |
281
|
|
|
// Check if consent is valid. (Possible that attributes has changed) |
282
|
|
|
if ($user_consent[$targeted_id] == $attribute_hash) { |
283
|
|
|
\SimpleSAML\Logger::info('consentAdmin: ok'); |
284
|
|
|
$sp_status = "ok"; |
285
|
|
|
} |
286
|
|
|
// Consent does not exist |
287
|
|
|
} else { |
288
|
|
|
SimpleSAML\Logger::info('consentAdmin: none'); |
289
|
|
|
$sp_status = "none"; |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
// Set name of SP |
293
|
|
|
if (isset($sp_values['name']) && is_array($sp_values['name'])) { |
294
|
|
|
$sp_name = $sp_metadata['name']; |
295
|
|
|
} else { |
296
|
|
|
if (isset($sp_values['name']) && is_string($sp_values['name'])) { |
297
|
|
|
$sp_name = $sp_metadata['name']; |
298
|
|
|
} elseif (isset($sp_values['OrganizationDisplayName']) && is_array($sp_values['OrganizationDisplayName'])) { |
299
|
|
|
$sp_name = $sp_metadata['OrganizationDisplayName']; |
300
|
|
|
} else { |
301
|
|
|
$sp_name = $sp_entityid; |
302
|
|
|
} |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
// Set description of SP |
306
|
|
|
if (empty($sp_metadata['description']) || !is_array($sp_metadata['description'])) { |
307
|
|
|
$sp_description = $sp_empty_description; |
308
|
|
|
} else { |
309
|
|
|
$sp_description = $sp_metadata['description']; |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
// Add a URL to the service if present in metadata |
313
|
|
|
$sp_service_url = isset($sp_metadata['ServiceURL']) ? $sp_metadata['ServiceURL'] : null; |
314
|
|
|
|
315
|
|
|
// Translate SP name and description |
316
|
|
|
$translator->includeInlineTranslation('spname', $sp_name); |
317
|
|
|
$translator->includeInlineTranslation('spdescription', $sp_description); |
318
|
|
|
|
319
|
|
|
$sp_name = $translator->getPreferredTranslation($translator->getTag('spname') ?? []); |
320
|
|
|
$sp_description = $translator->getPreferredTranslation($translator->getTag('spdescription') ?? []); |
321
|
|
|
|
322
|
|
|
// Fill out array for the template |
323
|
|
|
$sp_list[$sp_entityid] = [ |
324
|
|
|
'spentityid' => $sp_entityid, |
325
|
|
|
'name' => $sp_name, |
326
|
|
|
'description' => $sp_description, |
327
|
|
|
'consentStatus' => $sp_status, |
328
|
|
|
'consentValue' => $sp_entityid, |
329
|
|
|
'attributes_by_sp' => $attributes_new, |
330
|
|
|
'serviceurl' => $sp_service_url, |
331
|
|
|
]; |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
$template->data['header'] = 'Consent Administration'; |
335
|
|
|
$template->data['spList'] = $sp_list; |
336
|
|
|
$template->data['showDescription'] = $cA_config->getValue('showDescription'); |
337
|
|
|
$template->send(); |
338
|
|
|
|