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