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

ConsentAdmin::main()   F

Complexity

Conditions 29
Paths 4712

Size

Total Lines 230
Code Lines 128

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 29
eloc 128
c 1
b 0
f 0
nc 4712
nop 1
dl 0
loc 230
rs 0

How to fix   Long Method    Complexity   

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:

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