Passed
Pull Request — master (#3)
by Tim
02:51
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', false);
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
                $this->config,
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,
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
                    $consent_storage->deleteConsent($hashed_user_id, $targeted_id);
224
                    $isStored = false;
225
                } else {
226
                    Logger::info('consentAdmin: unknown action');
227
                    $isStored = null;
228
                }
229
            }
230
            $template->data['isStored'] = $isStored;
231
            return $template;
232
        }
233
234
        // Get all consents for user
235
        $user_consent_list = $consent_storage->getConsents($hashed_user_id);
236
237
        // Parse list of consents
238
        $user_consent = [];
239
        foreach ($user_consent_list as $c) {
240
           $user_consent[$c[0]] = $c[1];
241
        }
242
243
        $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...
244
245
        // Init template
246
        $template = new Template($this->config, 'consentAdmin:consentadmin.twig', 'consentAdmin:consentadmin');
247
        $translator = $template->getTranslator();
248
        $translator->includeLanguageFile('attributes'); // attribute listings translated by this dictionary
249
250
        $sp_empty_description = $translator->getTag('sp_empty_description');
251
        $sp_list = [];
252
253
        // Process consents for all SP
254
        foreach ($all_sp_metadata as $sp_entityid => $sp_values) {
255
            // Get metadata for SP
256
            $sp_metadata = $metadata->getMetaData($sp_entityid, 'saml20-sp-remote');
257
258
            // Run attribute filters
259
            list($targeted_id, $attribute_hash, $attributes_new) = $this->driveProcessingChain(
260
                $idp_metadata,
261
                $source,
262
                $sp_metadata,
263
                $sp_entityid,
264
                $attributes,
265
                $userid,
266
                $hashAttributes,
267
                $excludeAttributes
268
            );
269
270
            // Translate attribute-names
271
            foreach ($attributes_new as $orig_name => $value) {
272
                if (isset($template->data['attribute_' . htmlspecialchars(strtolower($orig_name))])) {
273
                    $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...
274
                }
275
                $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

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