ConsentAdmin::logout()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 1
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Module\consentAdmin\Controller;
6
7
use Exception;
8
use SimpleSAML\Auth;
9
use SimpleSAML\Configuration;
10
use SimpleSAML\Logger;
11
use SimpleSAML\Metadata\MetaDataStorageHandler;
12
use SimpleSAML\Module\consent\Auth\Process\Consent;
13
use SimpleSAML\Module\consent\Store;
14
use SimpleSAML\Session;
15
use SimpleSAML\XHTML\Template;
16
use Symfony\Component\HttpFoundation\Request;
17
18
use function array_key_exists;
19
use function in_array;
20
use function is_array;
21
use function sprintf;
22
use function strpos;
23
use function substr;
24
25
/**
26
 * Controller class for the consentadmin module.
27
 *
28
 * This class serves the different views available in the module.
29
 *
30
 * @package simplesamlphp/simplesamlphp-module-consentAdmin
31
 */
32
class ConsentAdmin
33
{
34
    /** @var \SimpleSAML\Configuration */
35
    protected Configuration $config;
36
37
    /** @var \SimpleSAML\Configuration */
38
    protected Configuration $moduleConfig;
39
40
    /** @var \SimpleSAML\Session */
41
    protected Session $session;
42
43
    /** @var \SimpleSAML\Metadata\MetaDataStorageHandler */
44
    protected MetaDataStorageHandler $metadataStorageHandler;
45
46
    /**
47
     * @var \SimpleSAML\Auth\Simple|string
48
     * @psalm-var \SimpleSAML\Auth\Simple|class-string
49
     */
50
    protected $authSimple = Auth\Simple::class;
51
52
    /**
53
     * @var \SimpleSAML\Module\consent\Auth\Process\Consent|string
54
     * @psalm-var \SimpleSAML\Module\consent\Auth\Process\Consent|class-string
55
     */
56
    protected $consent = Consent::class;
57
58
    /**
59
     * @var \SimpleSAML\Module\consent\Store|string
60
     * @psalm-var \SimpleSAML\Module\consent\Store|class-string
61
     */
62
    protected $store = Store::class;
63
64
65
    /**
66
     * Controller constructor.
67
     *
68
     * It initializes the global configuration and session for the controllers implemented here.
69
     *
70
     * @param \SimpleSAML\Configuration $config The configuration to use by the controllers.
71
     * @param \SimpleSAML\Session $session The session to use by the controllers.
72
     *
73
     * @throws \Exception
74
     */
75
    public function __construct(
76
        Configuration $config,
77
        Session $session,
78
    ) {
79
        $this->config = $config;
80
        $this->moduleConfig = Configuration::getConfig('module_consentAdmin.php');
81
        $this->metadataStorageHandler = MetaDataStorageHandler::getMetadataHandler();
82
        $this->session = $session;
83
    }
84
85
86
    /**
87
     * Inject the \SimpleSAML\Module\consent\Store dependency.
88
     *
89
     * @param \SimpleSAML\Module\consent\Store $store
90
     */
91
    public function setStore(Store $store): void
92
    {
93
        $this->store = $store;
94
    }
95
96
97
    /**
98
     * Inject the \SimpleSAML\Auth\Simple dependency.
99
     *
100
     * @param \SimpleSAML\Auth\Simple $authSimple
101
     */
102
    public function setAuthSimple(Auth\Simple $authSimple): void
103
    {
104
        $this->authSimple = $authSimple;
105
    }
106
107
108
    /**
109
     * Inject the \SimpleSAML\Metadata\MetaDataStorageHandler dependency.
110
     *
111
     * @param \SimpleSAML\Metadata\MetaDataStorageHandler $handler
112
     */
113
    public function setMetadataStorageHandler(MetadataStorageHandler $handler): void
114
    {
115
        $this->metadataStorageHandler = $handler;
116
    }
117
118
119
    /**
120
     * Inject the \SimpleSAML\Module\consent\Auth\Process\Consent dependency.
121
     *
122
     * @param \SimpleSAML\Module\consent\Auth\Process\Consent $consent
123
     */
124
    public function setConsent(Consent $consent): void
125
    {
126
        $this->consent = $consent;
127
    }
128
129
130
    /**
131
     * @param \Symfony\Component\HttpFoundation\Request $request The current request.
132
     *
133
     * @return void
134
     */
135
    public function logout(Request $request): void
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

135
    public function logout(/** @scrutinizer ignore-unused */ Request $request): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
136
    {
137
        $authority = $this->moduleConfig->getValue('authority');
138
        $as = new $this->authSimple($authority);
139
140
        $returnURL = $this->moduleConfig->getValue('returnURL');
141
        $as->logout($returnURL);
142
    }
143
144
145
    /**
146
     * @param \Symfony\Component\HttpFoundation\Request $request The current request.
147
     *
148
     * @return \SimpleSAML\XHTML\Template
149
     */
150
    public function main(Request $request): Template
151
    {
152
        $hashAttributes = $this->moduleConfig->getOptionalValue('attributes.hash', false);
153
        $excludeAttributes = $this->moduleConfig->getOptionalValue('attributes.exclude', []);
154
155
        // Check if valid local session exists
156
        $authority = $this->moduleConfig->getValue('authority');
157
        $as = new $this->authSimple($authority);
158
        $as->requireAuth();
159
160
        // Get released attributes
161
        $attributes = $as->getAttributes();
162
163
        // Get metadata storage handler
164
        $metadata = $this->metadataStorageHandler;
165
166
        /*
167
         * Get IdP id and metadata
168
         */
169
        $idp_entityid = $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted');
170
        $idp_metadata = $metadata->getMetaData($idp_entityid, 'saml20-idp-hosted');
171
172
        // Calc correct source
173
        if ($as->getAuthData('saml:sp:IdP') !== null) {
174
            // from a remote idp (as bridge)
175
            $source = 'saml20-idp-remote|' . $as->getAuthData('saml:sp:IdP');
176
        } else {
177
            // from the local idp
178
            $source = $idp_metadata['metadata-set'] . '|' . $idp_entityid;
179
        }
180
181
        // Get user ID
182
        $userid_attributename = $this->moduleConfig->getOptionalString(
183
            'identifyingAttribute',
184
            'eduPersonPrincipalName',
185
        );
186
        $userids = $attributes[$userid_attributename];
187
188
        if (empty($userids)) {
189
            throw new Exception(sprintf(
190
                'Could not generate useridentifier for storing consent. Attribute [%s] was not available.',
191
                $userid_attributename,
192
            ));
193
        }
194
195
        $userid = $userids[0];
196
197
        // Get all SP metadata
198
        $all_sp_metadata = $metadata->getList('saml20-sp-remote');
199
200
        $sp_entityid = $request->query->get('cv');
201
        $action = $request->query->get('action');
202
203
        Logger::notice('consentAdmin: sp: ' . $sp_entityid . ' action: ' . $action);
204
205
        // Remove services, whitch have consent disabled
206
        if (isset($idp_metadata['consent.disable'])) {
207
            foreach ($idp_metadata['consent.disable'] as $disable) {
208
                if (array_key_exists($disable, $all_sp_metadata)) {
209
                    unset($all_sp_metadata[$disable]);
210
                }
211
            }
212
        }
213
214
        Logger::info('consentAdmin: ' . $idp_entityid);
215
216
        // Parse consent config
217
        $consent_storage = $this->store::parseStoreConfig($this->moduleConfig->getValue('consentadmin'));
218
219
        // Calc correct user ID hash
220
        $hashed_user_id = $this->consent::getHashedUserID($userid, $source);
221
222
        // If a checkbox have been clicked
223
        if ($action !== null && $sp_entityid !== null) {
224
            // init template to enable translation of status messages
225
            $template = new Template(
226
                $this->config,
227
                'consentAdmin:consentadminajax.twig',
228
            );
229
230
            // Get SP metadata
231
            $sp_metadata = $metadata->getMetaData($sp_entityid, 'saml20-sp-remote');
232
233
            // Run AuthProc filters
234
            list($targeted_id, $attribute_hash, $attributes) = $this->driveProcessingChain(
235
                $idp_metadata,
236
                $source,
237
                $sp_metadata,
238
                $sp_entityid,
239
                $attributes,
240
                $userid,
241
                $hashAttributes,
242
                $excludeAttributes,
243
            );
244
245
            // Add a consent (or update if attributes have changed and old consent for SP and IdP exists)
246
            if ($action == 'true') {
247
                $isStored = $consent_storage->saveConsent($hashed_user_id, $targeted_id, $attribute_hash);
248
            } else {
249
                if ($action == 'false') {
250
                    // Got consent, so this is a request to remove it
251
                    $consent_storage->deleteConsent($hashed_user_id, $targeted_id);
252
                    $isStored = false;
253
                } else {
254
                    Logger::info('consentAdmin: unknown action');
255
                    $isStored = null;
256
                }
257
            }
258
            $template->data['isStored'] = $isStored;
259
            return $template;
260
        }
261
262
        // Get all consents for user
263
        $user_consent_list = $consent_storage->getConsents($hashed_user_id);
264
265
        // Parse list of consents
266
        $user_consent = [];
267
        foreach ($user_consent_list as $c) {
268
            $user_consent[$c[0]] = $c[1];
269
        }
270
271
        // Init template
272
        $template = new Template($this->config, 'consentAdmin:consentadmin.twig');
273
        $template->getLocalization()->addAttributeDomains();
274
275
        $sp_list = [];
276
277
        // Process consents for all SP
278
        foreach ($all_sp_metadata as $sp_entityid => $sp_values) {
279
            // Get metadata for SP
280
            $sp_metadata = $metadata->getMetaData($sp_entityid, 'saml20-sp-remote');
281
282
            // Run attribute filters
283
            list($targeted_id, $attribute_hash, $attributes) = $this->driveProcessingChain(
284
                $idp_metadata,
285
                $source,
286
                $sp_metadata,
287
                $sp_entityid,
288
                $attributes,
289
                $userid,
290
                $hashAttributes,
291
                $excludeAttributes,
292
            );
293
294
            // Check if consent exists
295
            if (array_key_exists($targeted_id, $user_consent)) {
296
                $sp_status = "changed";
297
                Logger::info('consentAdmin: changed');
298
                // Check if consent is valid. (Possible that attributes has changed)
299
                if ($user_consent[$targeted_id] == $attribute_hash) {
300
                    Logger::info('consentAdmin: ok');
301
                    $sp_status = "ok";
302
                }
303
                // Consent does not exist
304
            } else {
305
                Logger::info('consentAdmin: none');
306
                $sp_status = "none";
307
            }
308
309
            // Set description of SP
310
            $sp_description = null;
311
            if (!empty($sp_metadata['description']) && is_array($sp_metadata['description'])) {
312
                $sp_description = $sp_metadata['description'];
313
            }
314
315
            // Add a URL to the service if present in metadata
316
            $sp_service_url = isset($sp_metadata['ServiceURL']) ? $sp_metadata['ServiceURL'] : null;
317
318
            // Fill out array for the template
319
            $sp_list[$sp_entityid] = [
320
                'spentityid'       => $sp_entityid,
321
                'name'             => $template->getEntityDisplayName($sp_metadata),
322
                'description'      => $sp_description,
323
                'consentStatus'    => $sp_status,
324
                'consentValue'     => $sp_entityid,
325
                'attributes_by_sp' => $attributes,
326
                'serviceurl'       => $sp_service_url,
327
            ];
328
        }
329
330
        $template->data['header'] = 'Consent Administration';
331
        $template->data['spList'] = $sp_list;
332
        $template->data['showDescription'] = $this->moduleConfig->getBoolean('showDescription');
333
334
        return $template;
335
    }
336
337
338
    /**
339
     * Runs the processing chain and ignores all filter which have user
340
     * interaction.
341
     *
342
     * @param array<mixed> $idp_metadata
343
     * @param string $source
344
     * @param array<mixed> $sp_metadata
345
     * @param string $sp_entityid
346
     * @param array<mixed> $attributes
347
     * @param string $userid
348
     * @param bool $hashAttributes
349
     * @param string[] $excludeAttributes
350
     * @return array<mixed>
351
     */
352
    private function driveProcessingChain(
353
        array $idp_metadata,
354
        string $source,
355
        array $sp_metadata,
356
        string $sp_entityid,
357
        array $attributes,
358
        string $userid,
359
        bool $hashAttributes,
360
        array $excludeAttributes,
361
    ): array {
362
        /*
363
         * Create a new processing chain
364
         */
365
        $pc = new Auth\ProcessingChain($idp_metadata, $sp_metadata, 'idp');
366
367
        /*
368
         * Construct the state.
369
         * REMEMBER: Do not set Return URL if you are calling processStatePassive
370
         */
371
        $authProcState = [
372
            'Attributes'  => $attributes,
373
            'Destination' => $sp_metadata,
374
            'SPMetadata'  => $sp_metadata,
375
            'Source'      => $idp_metadata,
376
            'IdPMetadata' => $idp_metadata,
377
            'isPassive'   => true,
378
        ];
379
380
        /* we're being bridged, so add that info to the state */
381
        if (strpos($source, '-idp-remote|') !== false) {
382
            /** @var int<0, max> $i */
383
            $i = strpos($source, '|');
384
            $authProcState['saml:sp:IdP'] = substr($source, $i + 1);
385
        }
386
387
        /*
388
         * Call processStatePAssive.
389
         * We are not interested in any user interaction, only modifications to the attributes
390
         */
391
        $pc->processStatePassive($authProcState);
392
393
        $attributes = $authProcState['Attributes'];
394
        // Remove attributes that do not require consent/should be excluded
395
        foreach ($attributes as $attrkey => $attrval) {
396
            if (in_array($attrkey, $excludeAttributes)) {
397
                unset($attributes[$attrkey]);
398
            }
399
        }
400
401
        /*
402
         * Generate identifiers and hashes
403
         */
404
        $destination = $sp_metadata['metadata-set'] . '|' . $sp_entityid;
405
406
        $targeted_id = $this->consent::getTargetedID($userid, $source, $destination);
407
        $attribute_hash = $this->consent::getAttributeHash($attributes, $hashAttributes);
408
409
        Logger::info('consentAdmin: user: ' . $userid);
410
        Logger::info('consentAdmin: target: ' . $targeted_id);
411
        Logger::info('consentAdmin: attribute: ' . $attribute_hash);
412
413
        // Return values
414
        return [$targeted_id, $attribute_hash, $attributes];
415
    }
416
}
417