ConsentAdmin::driveProcessingChain()   A
last analyzed

Complexity

Conditions 4
Paths 6

Size

Total Lines 63
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 23
nc 6
nop 8
dl 0
loc 63
rs 9.552
c 0
b 0
f 0

How to fix   Long Method    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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