Passed
Pull Request — master (#3)
by Tim
02:58
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
     * @var \SimpleSAML\Module\consent\Store|string
50
     * @psalm-var \SimpleSAML\Module\consent\Store|class-string
51
     */
52
    protected $store = Store::class;
53
54
55
    /**
56
     * Controller constructor.
57
     *
58
     * It initializes the global configuration and session for the controllers implemented here.
59
     *
60
     * @param \SimpleSAML\Configuration $config The configuration to use by the controllers.
61
     * @param \SimpleSAML\Session $session The session to use by the controllers.
62
     *
63
     * @throws \Exception
64
     */
65
    public function __construct(
66
        Configuration $config,
67
        Session $session
68
    ) {
69
        $this->config = $config;
70
        $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...
71
        $this->metadataStorageHandler = MetaDataStorageHandler::getMetadataHandler();
72
        $this->session = $session;
73
    }
74
75
76
    /**
77
     * Inject the \SimpleSAML\Module\consent\Store dependency.
78
     *
79
     * @param \SimpleSAML\Module\consent\Store $store
80
     */
81
    public function setStore(Store $store): void
82
    {
83
        $this->store = $store;
84
    }
85
86
87
    /**
88
     * Inject the \SimpleSAML\Auth\Simple dependency.
89
     *
90
     * @param \SimpleSAML\Auth\Simple $authSimple
91
     */
92
    public function setAuthSimple(Auth\Simple $authSimple): void
93
    {
94
        $this->authSimple = $authSimple;
95
    }
96
97
98
    /**
99
     * Inject the \SimpleSAML\Metadata\MetaDataStorageHandler dependency.
100
     *
101
     * @param \SimpleSAML\Metadata\MetaDataStorageHandler $handler
102
     */
103
    public function setMetadataStorageHandler(MetadataStorageHandler $handler): void
104
    {
105
        $this->metadataStorageHandler = $handler;
106
    }
107
108
109
    /**
110
     * Inject the \SimpleSAML\Module\consent\Auth\Process\Consent dependency.
111
     *
112
     * @param \SimpleSAML\Module\consent\Auth\Process\Consent $consent
113
     */
114
    public function setConsent(Consent $consent): void
115
    {
116
        $this->consent = $consent;
117
    }
118
119
120
    /**
121
     * @param \Symfony\Component\HttpFoundation\Request $request The current request.
122
     *
123
     * @return \SimpleSAML\XHTML\Template
124
     */
125
    public function main(Request $request): Template
126
    {
127
        $authority = $this->moduleConfig->getValue('authority');
128
129
        $as = new $this->authSimple($authority);
130
131
        // If request is a logout request
132
        $logout = $request->get('logout');
133
        if ($logout !== null) {
134
            $returnURL = $this->moduleConfig->getValue('returnURL');
135
            $as->logout($returnURL);
136
        }
137
138
        $hashAttributes = $this->moduleConfig->getValue('attributes.hash', false);
139
140
        $excludeAttributes = $this->moduleConfig->getValue('attributes.exclude', []);
141
142
        // Check if valid local session exists
143
        $as->requireAuth();
144
145
        // Get released attributes
146
        $attributes = $as->getAttributes();
147
148
        // Get metadata storage handler
149
        $metadata = $this->metadataStorageHandler;
150
151
        /*
152
         * Get IdP id and metadata
153
         */
154
        $idp_entityid = $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted');
155
        $idp_metadata = $metadata->getMetaData($idp_entityid, 'saml20-idp-hosted');
156
157
        // Calc correct source
158
        if ($as->getAuthData('saml:sp:IdP') !== null) {
159
            // from a remote idp (as bridge)
160
            $source = 'saml20-idp-remote|' . $as->getAuthData('saml:sp:IdP');
161
        } else {
162
            // from the local idp
163
            $source = $idp_metadata['metadata-set'] . '|' . $idp_entityid;
164
        }
165
166
        // Get user ID
167
        if (isset($idp_metadata['userid.attribute']) && is_string($idp_metadata['userid.attribute'])) {
168
            $userid_attributename = $idp_metadata['userid.attribute'];
169
        } else {
170
            $userid_attributename = 'eduPersonPrincipalName';
171
        }
172
173
        $userids = $attributes[$userid_attributename];
174
175
        if (empty($userids)) {
176
            throw new Exception(sprintf(
177
                'Could not generate useridentifier for storing consent. Attribute [%s] was not available.',
178
                $userid_attributename
179
            ));
180
        }
181
182
        $userid = $userids[0];
183
184
        // Get all SP metadata
185
        $all_sp_metadata = $metadata->getList('saml20-sp-remote');
186
187
        $sp_entityid = $request->get('cv');;
188
        $action = $request->get('action');
189
190
        Logger::critical('consentAdmin: sp: ' . $sp_entityid . ' action: ' . $action);
191
192
        // Remove services, whitch have consent disabled
193
        if (isset($idp_metadata['consent.disable'])) {
194
            foreach ($idp_metadata['consent.disable'] as $disable) {
195
                if (array_key_exists($disable, $all_sp_metadata)) {
196
                    unset($all_sp_metadata[$disable]);
197
                }
198
            }
199
        }
200
201
        Logger::info('consentAdmin: ' . $idp_entityid);
202
203
        // Parse consent config
204
        $consent_storage = $this->store::parseStoreConfig($this->moduleConfig->getValue('consentadmin'));
205
206
        // Calc correct user ID hash
207
        $hashed_user_id = $this->consent::getHashedUserID($userid, $source);
208
209
        // If a checkbox have been clicked
210
        if ($action !== null && $sp_entityid !== null) {
211
            // init template to enable translation of status messages
212
            $template = new Template(
213
                $this->config,
214
                'consentAdmin:consentadminajax.twig',
215
                'consentAdmin:consentadmin'
216
            );
217
            $translator = $template->getTranslator();
0 ignored issues
show
Unused Code introduced by
The assignment to $translator is dead and can be removed.
Loading history...
218
219
            // Get SP metadata
220
            $sp_metadata = $metadata->getMetaData($sp_entityid, 'saml20-sp-remote');
221
222
            // Run AuthProc filters
223
            list($targeted_id, $attribute_hash, $attributes_new) = $this->driveProcessingChain(
224
                $idp_metadata,
225
                $source,
226
                $sp_metadata,
227
                $sp_entityid,
228
                $attributes,
229
                $userid,
230
                $hashAttributes,
231
                $excludeAttributes
232
            );
233
234
            // Add a consent (or update if attributes have changed and old consent for SP and IdP exists)
235
            if ($action == 'true') {
236
                $isStored = $consent_storage->saveConsent($hashed_user_id, $targeted_id, $attribute_hash);
237
            } else {
238
                if ($action == 'false') {
239
                    // Got consent, so this is a request to remove it
240
                    $consent_storage->deleteConsent($hashed_user_id, $targeted_id);
241
                    $isStored = false;
242
                } else {
243
                    Logger::info('consentAdmin: unknown action');
244
                    $isStored = null;
245
                }
246
            }
247
            $template->data['isStored'] = $isStored;
248
            return $template;
249
        }
250
251
        // Get all consents for user
252
        $user_consent_list = $consent_storage->getConsents($hashed_user_id);
253
254
        // Parse list of consents
255
        $user_consent = [];
256
        foreach ($user_consent_list as $c) {
257
           $user_consent[$c[0]] = $c[1];
258
        }
259
260
        $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...
261
262
        // Init template
263
        $template = new Template($this->config, 'consentAdmin:consentadmin.twig', 'consentAdmin:consentadmin');
264
        $template->getLocalization()->addAttributeDomains();
265
        $translator = $template->getTranslator();
266
267
        $sp_list = [];
268
269
        // Process consents for all SP
270
        foreach ($all_sp_metadata as $sp_entityid => $sp_values) {
271
            // Get metadata for SP
272
            $sp_metadata = $metadata->getMetaData($sp_entityid, 'saml20-sp-remote');
273
274
            // Run attribute filters
275
            list($targeted_id, $attribute_hash, $attributes_new) = $this->driveProcessingChain(
276
                $idp_metadata,
277
                $source,
278
                $sp_metadata,
279
                $sp_entityid,
280
                $attributes,
281
                $userid,
282
                $hashAttributes,
283
                $excludeAttributes
284
            );
285
286
            // Translate attribute-names
287
            foreach ($attributes_new as $orig_name => $value) {
288
                if (isset($template->data['attribute_' . htmlspecialchars(strtolower($orig_name))])) {
289
                    $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...
290
                }
291
                $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

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