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
                'consentAdmin:consentadmin',
0 ignored issues
show
Unused Code introduced by
The call to SimpleSAML\XHTML\Template::__construct() has too many arguments starting with 'consentAdmin:consentadmin'. ( Ignorable by Annotation )

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

228
            $template = /** @scrutinizer ignore-call */ new Template(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
229
            );
230
231
            // Get SP metadata
232
            $sp_metadata = $metadata->getMetaData($sp_entityid, 'saml20-sp-remote');
233
234
            // Run AuthProc filters
235
            list($targeted_id, $attribute_hash, $attributes) = $this->driveProcessingChain(
236
                $idp_metadata,
237
                $source,
238
                $sp_metadata,
239
                $sp_entityid,
240
                $attributes,
241
                $userid,
242
                $hashAttributes,
243
                $excludeAttributes,
244
            );
245
246
            // Add a consent (or update if attributes have changed and old consent for SP and IdP exists)
247
            if ($action == 'true') {
248
                $isStored = $consent_storage->saveConsent($hashed_user_id, $targeted_id, $attribute_hash);
249
            } else {
250
                if ($action == 'false') {
251
                    // Got consent, so this is a request to remove it
252
                    $consent_storage->deleteConsent($hashed_user_id, $targeted_id);
253
                    $isStored = false;
254
                } else {
255
                    Logger::info('consentAdmin: unknown action');
256
                    $isStored = null;
257
                }
258
            }
259
            $template->data['isStored'] = $isStored;
260
            return $template;
261
        }
262
263
        // Get all consents for user
264
        $user_consent_list = $consent_storage->getConsents($hashed_user_id);
265
266
        // Parse list of consents
267
        $user_consent = [];
268
        foreach ($user_consent_list as $c) {
269
            $user_consent[$c[0]] = $c[1];
270
        }
271
272
        // Init template
273
        $template = new Template($this->config, 'consentAdmin:consentadmin.twig', 'consentAdmin:consentadmin');
274
        $template->getLocalization()->addAttributeDomains();
275
276
        $sp_list = [];
277
278
        // Process consents for all SP
279
        foreach ($all_sp_metadata as $sp_entityid => $sp_values) {
280
            // Get metadata for SP
281
            $sp_metadata = $metadata->getMetaData($sp_entityid, 'saml20-sp-remote');
282
283
            // Run attribute filters
284
            list($targeted_id, $attribute_hash, $attributes) = $this->driveProcessingChain(
285
                $idp_metadata,
286
                $source,
287
                $sp_metadata,
288
                $sp_entityid,
289
                $attributes,
290
                $userid,
291
                $hashAttributes,
292
                $excludeAttributes,
293
            );
294
295
            // Check if consent exists
296
            if (array_key_exists($targeted_id, $user_consent)) {
297
                $sp_status = "changed";
298
                Logger::info('consentAdmin: changed');
299
                // Check if consent is valid. (Possible that attributes has changed)
300
                if ($user_consent[$targeted_id] == $attribute_hash) {
301
                    Logger::info('consentAdmin: ok');
302
                    $sp_status = "ok";
303
                }
304
                // Consent does not exist
305
            } else {
306
                Logger::info('consentAdmin: none');
307
                $sp_status = "none";
308
            }
309
310
            // Set description of SP
311
            $sp_description = null;
312
            if (!empty($sp_metadata['description']) && is_array($sp_metadata['description'])) {
313
                $sp_description = $sp_metadata['description'];
314
            }
315
316
            // Add a URL to the service if present in metadata
317
            $sp_service_url = isset($sp_metadata['ServiceURL']) ? $sp_metadata['ServiceURL'] : null;
318
319
            // Fill out array for the template
320
            $sp_list[$sp_entityid] = [
321
                'spentityid'       => $sp_entityid,
322
                'name'             => $template->getEntityDisplayName($sp_metadata),
323
                'description'      => $sp_description,
324
                'consentStatus'    => $sp_status,
325
                'consentValue'     => $sp_entityid,
326
                'attributes_by_sp' => $attributes,
327
                'serviceurl'       => $sp_service_url,
328
            ];
329
        }
330
331
        $template->data['header'] = 'Consent Administration';
332
        $template->data['spList'] = $sp_list;
333
        $template->data['showDescription'] = $this->moduleConfig->getBoolean('showDescription');
334
335
        return $template;
336
    }
337
338
339
    /**
340
     * Runs the processing chain and ignores all filter which have user
341
     * interaction.
342
     *
343
     * @param array $idp_metadata
344
     * @param string $source
345
     * @param array $sp_metadata
346
     * @param string $sp_entityid
347
     * @param array $attributes
348
     * @param string $userid
349
     * @param bool $hashAttributes
350
     * @param array $excludeAttributes
351
     * @return array
352
     */
353
    private function driveProcessingChain(
354
        array $idp_metadata,
355
        string $source,
356
        array $sp_metadata,
357
        string $sp_entityid,
358
        array $attributes,
359
        string $userid,
360
        bool $hashAttributes,
361
        array $excludeAttributes,
362
    ): array {
363
        /*
364
         * Create a new processing chain
365
         */
366
        $pc = new Auth\ProcessingChain($idp_metadata, $sp_metadata, 'idp');
367
368
        /*
369
         * Construct the state.
370
         * REMEMBER: Do not set Return URL if you are calling processStatePassive
371
         */
372
        $authProcState = [
373
            'Attributes'  => $attributes,
374
            'Destination' => $sp_metadata,
375
            'SPMetadata'  => $sp_metadata,
376
            'Source'      => $idp_metadata,
377
            'IdPMetadata' => $idp_metadata,
378
            'isPassive'   => true,
379
        ];
380
381
        /* we're being bridged, so add that info to the state */
382
        if (strpos($source, '-idp-remote|') !== false) {
383
            /** @var int $i */
384
            $i = strpos($source, '|');
385
            $authProcState['saml:sp:IdP'] = substr($source, $i + 1);
386
        }
387
388
        /*
389
         * Call processStatePAssive.
390
         * We are not interested in any user interaction, only modifications to the attributes
391
         */
392
        $pc->processStatePassive($authProcState);
393
394
        $attributes = $authProcState['Attributes'];
395
        // Remove attributes that do not require consent/should be excluded
396
        foreach ($attributes as $attrkey => $attrval) {
397
            if (in_array($attrkey, $excludeAttributes)) {
398
                unset($attributes[$attrkey]);
399
            }
400
        }
401
402
        /*
403
         * Generate identifiers and hashes
404
         */
405
        $destination = $sp_metadata['metadata-set'] . '|' . $sp_entityid;
406
407
        $targeted_id = $this->consent::getTargetedID($userid, $source, $destination);
408
        $attribute_hash = $this->consent::getAttributeHash($attributes, $hashAttributes);
409
410
        Logger::info('consentAdmin: user: ' . $userid);
411
        Logger::info('consentAdmin: target: ' . $targeted_id);
412
        Logger::info('consentAdmin: attribute: ' . $attribute_hash);
413
414
        // Return values
415
        return [$targeted_id, $attribute_hash, $attributes];
416
    }
417
}
418