Passed
Pull Request — master (#3)
by Tim
08:13
created

ConsentAdmin::driveProcessingChain()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 63
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

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

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
/**
19
 * Controller class for the consentadmin module.
20
 *
21
 * This class serves the different views available in the module.
22
 *
23
 * @package simplesamlphp/simplesamlphp-module-consentadmin
24
 */
25
class ConsentAdmin
26
{
27
    /** @var \SimpleSAML\Configuration */
28
    protected Configuration $config;
29
30
    /** @var \SimpleSAML\Session */
31
    protected Session $session;
32
33
    /** @var \SimpleSAML\Metadata\MetaDataStorageHandler */
34
    protected MetaDataStorageHandler $metadataStorageHandler;
35
36
    /**
37
     * @var \SimpleSAML\Auth\Simple|string
38
     * @psalm-var \SimpleSAML\Auth\Simple|class-string
39
     */
40
    protected $authSimple = Auth\Simple::class;
41
42
    /**
43
     * @var \SimpleSAML\Module\consent\Auth\Process\Consent|string
44
     * @psalm-var \SimpleSAML\Module\consent\Auth\Process\Consent|class-string
45
     */
46
    protected $consent = Consent::class;
47
48
49
    /**
50
     * Controller constructor.
51
     *
52
     * It initializes the global configuration and session for the controllers implemented here.
53
     *
54
     * @param \SimpleSAML\Configuration $config The configuration to use by the controllers.
55
     * @param \SimpleSAML\Session $session The session to use by the controllers.
56
     *
57
     * @throws \Exception
58
     */
59
    public function __construct(
60
        Configuration $config,
61
        Session $session
62
    ) {
63
        $this->config = $config;
64
        $this->moduleConfig = Configuration::getConfig('module_consentAdmin.php');
0 ignored issues
show
Bug Best Practice introduced by
The property moduleConfig does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
65
        $this->metadataStorageHandler = MetaDataStorageHandler::getMetadataHandler();
66
        $this->session = $session;
67
    }
68
69
70
    /**
71
     * Inject the \SimpleSAML\Auth\Simple dependency.
72
     *
73
     * @param \SimpleSAML\Auth\Simple $authSimple
74
     */
75
    public function setAuthSimple(Auth\Simple $authSimple): void
76
    {
77
        $this->authSimple = $authSimple;
78
    }
79
80
81
    /**
82
     * Inject the \SimpleSAML\Metadata\MetaDataStorageHandler dependency.
83
     *
84
     * @param \SimpleSAML\Metadata\MetaDataStorageHandler $handler
85
     */
86
    public function setMetadataStorageHandler(MetadataStorageHandler $handler): void
87
    {
88
        $this->metadataStorageHandler = $handler;
89
    }
90
91
92
    /**
93
     * Inject the \SimpleSAML\Module\consent\Auth\Process\Consent dependency.
94
     *
95
     * @param \SimpleSAML\Module\consent\Auth\Process\Consent $consent
96
     */
97
    public function setConsent(Consent $consent): void
98
    {
99
        $this->consent = $consent;
100
    }
101
102
103
    /**
104
     * @param \Symfony\Component\HttpFoundation\Request $request The current request.
105
     *
106
     * @return \SimpleSAML\XHTML\Template
107
     */
108
    public function main(Request $request): Template
109
    {
110
        $authority = $this->moduleConfig->getValue('authority');
111
112
        $as = new $this->authSimple($authority);
113
114
        // If request is a logout request
115
        $logout = $request->get('logout');
116
        if ($logout !== null) {
117
            $returnURL = $this->moduleConfig->getValue('returnURL');
118
            $as->logout($returnURL);
119
        }
120
121
        $hashAttributes = $this->moduleConfig->getValue('attributes.hash');
122
123
        $excludeAttributes = $this->moduleConfig->getValue('attributes.exclude', []);
124
125
        // Check if valid local session exists
126
        $as->requireAuth();
127
128
        // Get released attributes
129
        $attributes = $as->getAttributes();
130
131
        // Get metadata storage handler
132
        $metadata = $this->metadataStorageHandler;
133
134
        /*
135
         * Get IdP id and metadata
136
         */
137
        $idp_entityid = $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted');
138
        $idp_metadata = $metadata->getMetaData($idp_entityid, 'saml20-idp-hosted');
139
140
        // Calc correct source
141
        if ($as->getAuthData('saml:sp:IdP') !== null) {
142
            // from a remote idp (as bridge)
143
            $source = 'saml20-idp-remote|' . $as->getAuthData('saml:sp:IdP');
144
        } else {
145
            // from the local idp
146
            $source = $idp_metadata['metadata-set'] . '|' . $idp_entityid;
147
        }
148
149
        // Get user ID
150
        if (isset($idp_metadata['userid.attribute']) && is_string($idp_metadata['userid.attribute'])) {
151
            $userid_attributename = $idp_metadata['userid.attribute'];
152
        } else {
153
            $userid_attributename = 'eduPersonPrincipalName';
154
        }
155
156
        $userids = $attributes[$userid_attributename];
157
158
        if (empty($userids)) {
159
            throw new Exception(sprintf(
160
                'Could not generate useridentifier for storing consent. Attribute [%s] was not available.',
161
                $userid_attributename
162
            ));
163
        }
164
165
        $userid = $userids[0];
166
167
        // Get all SP metadata
168
        $all_sp_metadata = $metadata->getList('saml20-sp-remote');
169
170
        $sp_entityid = $request->get('cv');;
171
        $action = $request->get('action');
172
173
        Logger::critical('consentAdmin: sp: ' . $sp_entityid . ' action: ' . $action);
174
175
        // Remove services, whitch have consent disabled
176
        if (isset($idp_metadata['consent.disable'])) {
177
            foreach ($idp_metadata['consent.disable'] as $disable) {
178
                if (array_key_exists($disable, $all_sp_metadata)) {
179
                    unset($all_sp_metadata[$disable]);
180
                }
181
            }
182
        }
183
184
        Logger::info('consentAdmin: ' . $idp_entityid);
185
186
        // Parse consent config
187
        $consent_storage = Store::parseStoreConfig($this->moduleConfig->getValue('consentadmin'));
188
189
        // Calc correct user ID hash
190
        $hashed_user_id = $this->consent::getHashedUserID($userid, $source);
191
192
        // If a checkbox have been clicked
193
        if ($action !== null && $sp_entityid !== null) {
194
            // init template to enable translation of status messages
195
            $template = new Template(
196
                $config,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $config seems to be never defined.
Loading history...
197
                'consentAdmin:consentadminajax.twig',
198
                'consentAdmin:consentadmin'
199
            );
200
            $translator = $template->getTranslator();
0 ignored issues
show
Unused Code introduced by
The assignment to $translator is dead and can be removed.
Loading history...
201
202
            // Get SP metadata
203
            $sp_metadata = $metadata->getMetaData($sp_entityid, 'saml20-sp-remote');
204
205
            // Run AuthProc filters
206
            list($targeted_id, $attribute_hash, $attributes_new) = $this->driveProcessingChain(
207
                $idp_metadata,
208
                $source,
209
                $sp_metadata,
210
                $sp_entityid,
211
                $attributes,
212
                $userid,
213
                $hashAttributes,
0 ignored issues
show
Bug introduced by
It seems like $hashAttributes can also be of type null; however, parameter $hashAttributes of SimpleSAML\Module\consen...:driveProcessingChain() does only seem to accept boolean, maybe add an additional type check? ( Ignorable by Annotation )

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

213
                /** @scrutinizer ignore-type */ $hashAttributes,
Loading history...
214
                $excludeAttributes
215
            );
216
217
            // Add a consent (or update if attributes have changed and old consent for SP and IdP exists)
218
            if ($action == 'true') {
219
                $isStored = $consent_storage->saveConsent($hashed_user_id, $targeted_id, $attribute_hash);
220
            } else {
221
                if ($action == 'false') {
222
                    // Got consent, so this is a request to remove it
223
                    $rowcount = $consent_storage->deleteConsent($hashed_user_id, $targeted_id);
224
                    if ($rowcount > 0) {
225
                        $isStored = false;
226
                    } else {
227
                        throw new Exception("Unknown action (should not happen)");
228
                    }
229
                } else {
230
                    Logger::info('consentAdmin: unknown action');
231
                    $isStored = null;
232
                }
233
            }
234
            $template->data['isStored'] = $isStored;
235
            return $template;
236
        }
237
238
        // Get all consents for user
239
        $user_consent_list = $consent_storage->getConsents($hashed_user_id);
240
241
        // Parse list of consents
242
        $user_consent = [];
243
        foreach ($user_consent_list as $c) {
244
           $user_consent[$c[0]] = $c[1];
245
        }
246
247
        $template_sp_content = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $template_sp_content is dead and can be removed.
Loading history...
248
249
        // Init template
250
        $template = new Template($config, 'consentAdmin:consentadmin.twig', 'consentAdmin:consentadmin');
251
        $translator = $template->getTranslator();
252
        $translator->includeLanguageFile('attributes'); // attribute listings translated by this dictionary
253
254
        $sp_empty_description = $translator->getTag('sp_empty_description');
255
        $sp_list = [];
256
257
        // Process consents for all SP
258
        foreach ($all_sp_metadata as $sp_entityid => $sp_values) {
259
            // Get metadata for SP
260
            $sp_metadata = $metadata->getMetaData($sp_entityid, 'saml20-sp-remote');
261
262
            // Run attribute filters
263
            list($targeted_id, $attribute_hash, $attributes_new) = $this->driveProcessingChain(
264
                $idp_metadata,
265
                $source,
266
                $sp_metadata,
267
                $sp_entityid,
268
                $attributes,
269
                $userid,
270
                $hashAttributes,
271
                $excludeAttributes
272
            );
273
274
            // Translate attribute-names
275
            foreach ($attributes_new as $orig_name => $value) {
276
                if (isset($template->data['attribute_' . htmlspecialchars(strtolower($orig_name))])) {
277
                    $old_name = $template->data['attribute_' . htmlspecialchars(strtolower($orig_name))];
0 ignored issues
show
Unused Code introduced by
The assignment to $old_name is dead and can be removed.
Loading history...
278
                }
279
                $name = $translator->getAttributeTranslation(strtolower($orig_name)); // translate
0 ignored issues
show
Bug introduced by
The method getAttributeTranslation() does not exist on SimpleSAML\Locale\Translate. ( Ignorable by Annotation )

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

279
                /** @scrutinizer ignore-call */ 
280
                $name = $translator->getAttributeTranslation(strtolower($orig_name)); // translate

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
280
281
                $attributes_new[$name] = $value;
282
                unset($attributes_new[$orig_name]);
283
            }
284
285
            // Check if consent exists
286
            if (array_key_exists($targeted_id, $user_consent)) {
287
                $sp_status = "changed";
288
                Logger::info('consentAdmin: changed');
289
                // Check if consent is valid. (Possible that attributes has changed)
290
                if ($user_consent[$targeted_id] == $attribute_hash) {
291
                    Logger::info('consentAdmin: ok');
292
                    $sp_status = "ok";
293
                }
294
                // Consent does not exist
295
            } else {
296
                Logger::info('consentAdmin: none');
297
                $sp_status = "none";
298
            }
299
300
            // Set name of SP
301
            if (isset($sp_values['name']) && is_array($sp_values['name'])) {
302
                $sp_name = $sp_metadata['name'];
303
            } else {
304
                if (isset($sp_values['name']) && is_string($sp_values['name'])) {
305
                    $sp_name = $sp_metadata['name'];
306
                } elseif (isset($sp_values['OrganizationDisplayName']) && is_array($sp_values['OrganizationDisplayName'])) {
307
                    $sp_name = $sp_metadata['OrganizationDisplayName'];
308
                }
309
            }
310
311
            // Set description of SP
312
            if (empty($sp_metadata['description']) || !is_array($sp_metadata['description'])) {
313
                $sp_description = $sp_empty_description;
314
            } else {
315
                $sp_description = $sp_metadata['description'];
316
            }
317
318
            // Add a URL to the service if present in metadata
319
            $sp_service_url = isset($sp_metadata['ServiceURL']) ? $sp_metadata['ServiceURL'] : null;
320
321
            // Fill out array for the template
322
            $sp_list[$sp_entityid] = [
323
                'spentityid'       => $sp_entityid,
324
                'name'             => $sp_name,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sp_name does not seem to be defined for all execution paths leading up to this point.
Loading history...
325
                'description'      => $sp_description,
326
                'consentStatus'    => $sp_status,
327
                'consentValue'     => $sp_entityid,
328
                'attributes_by_sp' => $attributes_new,
329
                'serviceurl'       => $sp_service_url,
330
            ];
331
        }
332
333
        $template->data['header'] = 'Consent Administration';
334
        $template->data['spList'] = $sp_list;
335
        $template->data['showDescription'] = $cA_config->getValue('showDescription');
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $cA_config seems to be never defined.
Loading history...
336
337
        return $template;
338
    }
339
340
341
    /**
342
     * Runs the processing chain and ignores all filter which have user
343
     * interaction.
344
     *
345
     * @param array $idp_metadata
346
     * @param string $source
347
     * @param array $sp_metadata
348
     * @param string $sp_entityid
349
     * @param array $attributes
350
     * @param string $userid
351
     * @param bool $hashAttributes
352
     * @param array $excludeAttributes
353
     * @return array
354
     */
355
    private function driveProcessingChain(
356
        array $idp_metadata,
357
        string $source,
358
        array $sp_metadata,
359
        string $sp_entityid,
360
        array $attributes,
361
        string $userid,
362
        bool $hashAttributes = false,
363
        array $excludeAttributes = []
364
    ): array {
365
        /*
366
         * Create a new processing chain
367
         */
368
        $pc = new Auth\ProcessingChain($idp_metadata, $sp_metadata, 'idp');
369
370
        /*
371
         * Construct the state.
372
         * REMEMBER: Do not set Return URL if you are calling processStatePassive
373
         */
374
        $authProcState = [
375
            'Attributes'  => $attributes,
376
            'Destination' => $sp_metadata,
377
            'SPMetadata'  => $sp_metadata,
378
            'Source'      => $idp_metadata,
379
            'IdPMetadata' => $idp_metadata,
380
            'isPassive'   => true,
381
        ];
382
383
        /* we're being bridged, so add that info to the state */
384
        if (strpos($source, '-idp-remote|') !== false) {
385
            /** @var int $i */
386
            $i = strpos($source, '|');
387
            $authProcState['saml:sp:IdP'] = substr($source, $i + 1);
388
        }
389
390
        /*
391
         * Call processStatePAssive.
392
         * We are not interested in any user interaction, only modifications to the attributes
393
         */
394
        $pc->processStatePassive($authProcState);
395
396
        $attributes = $authProcState['Attributes'];
397
        // Remove attributes that do not require consent/should be excluded
398
        foreach ($attributes as $attrkey => $attrval) {
399
            if (in_array($attrkey, $excludeAttributes)) {
400
                unset($attributes[$attrkey]);
401
            }
402
        }
403
404
        /*
405
         * Generate identifiers and hashes
406
         */
407
        $destination = $sp_metadata['metadata-set'] . '|' . $sp_entityid;
408
409
        $targeted_id = Consent::getTargetedID($userid, $source, $destination);
410
        $attribute_hash = Consent::getAttributeHash($attributes, $hashAttributes);
411
412
        Logger::info('consentAdmin: user: ' . $userid);
413
        Logger::info('consentAdmin: target: ' . $targeted_id);
414
        Logger::info('consentAdmin: attribute: ' . $attribute_hash);
415
416
        // Return values
417
        return [$targeted_id, $attribute_hash, $attributes];
418
    }
419
}
420