Passed
Pull Request — master (#3)
by Tim
02:58
created

ConsentAdmin::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 8
rs 10
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