ConsentAdmin::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 2
dl 0
loc 8
rs 10
c 0
b 0
f 0
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
use function array_key_exists;
19
use function in_array;
20
use function is_array;
21
use function sprintf;
22
use function strpos;
23
use function substr;
24
25
/**
26
 * Controller class for the consentadmin module.
27
 *
28
 * This class serves the different views available in the module.
29
 *
30
 * @package simplesamlphp/simplesamlphp-module-consentAdmin
31
 */
32
class ConsentAdmin
33
{
34
    /** @var \SimpleSAML\Configuration */
35
    protected Configuration $config;
36
37
    /** @var \SimpleSAML\Configuration */
38
    protected Configuration $moduleConfig;
39
40
    /** @var \SimpleSAML\Session */
41
    protected Session $session;
42
43
    /** @var \SimpleSAML\Metadata\MetaDataStorageHandler */
44
    protected MetaDataStorageHandler $metadataStorageHandler;
45
46
    /**
47
     * @var \SimpleSAML\Auth\Simple|string
48
     * @psalm-var \SimpleSAML\Auth\Simple|class-string
49
     */
50
    protected $authSimple = Auth\Simple::class;
51
52
    /**
53
     * @var \SimpleSAML\Module\consent\Auth\Process\Consent|string
54
     * @psalm-var \SimpleSAML\Module\consent\Auth\Process\Consent|class-string
55
     */
56
    protected $consent = Consent::class;
57
58
    /**
59
     * @var \SimpleSAML\Module\consent\Store|string
60
     * @psalm-var \SimpleSAML\Module\consent\Store|class-string
61
     */
62
    protected $store = Store::class;
63
64
65
    /**
66
     * Controller constructor.
67
     *
68
     * It initializes the global configuration and session for the controllers implemented here.
69
     *
70
     * @param \SimpleSAML\Configuration $config The configuration to use by the controllers.
71
     * @param \SimpleSAML\Session $session The session to use by the controllers.
72
     *
73
     * @throws \Exception
74
     */
75
    public function __construct(
76
        Configuration $config,
77
        Session $session,
78
    ) {
79
        $this->config = $config;
80
        $this->moduleConfig = Configuration::getConfig('module_consentAdmin.php');
81
        $this->metadataStorageHandler = MetaDataStorageHandler::getMetadataHandler();
82
        $this->session = $session;
83
    }
84
85
86
    /**
87
     * Inject the \SimpleSAML\Module\consent\Store dependency.
88
     *
89
     * @param \SimpleSAML\Module\consent\Store $store
90
     */
91
    public function setStore(Store $store): void
92
    {
93
        $this->store = $store;
94
    }
95
96
97
    /**
98
     * Inject the \SimpleSAML\Auth\Simple dependency.
99
     *
100
     * @param \SimpleSAML\Auth\Simple $authSimple
101
     */
102
    public function setAuthSimple(Auth\Simple $authSimple): void
103
    {
104
        $this->authSimple = $authSimple;
105
    }
106
107
108
    /**
109
     * Inject the \SimpleSAML\Metadata\MetaDataStorageHandler dependency.
110
     *
111
     * @param \SimpleSAML\Metadata\MetaDataStorageHandler $handler
112
     */
113
    public function setMetadataStorageHandler(MetadataStorageHandler $handler): void
114
    {
115
        $this->metadataStorageHandler = $handler;
116
    }
117
118
119
    /**
120
     * Inject the \SimpleSAML\Module\consent\Auth\Process\Consent dependency.
121
     *
122
     * @param \SimpleSAML\Module\consent\Auth\Process\Consent $consent
123
     */
124
    public function setConsent(Consent $consent): void
125
    {
126
        $this->consent = $consent;
127
    }
128
129
130
    /**
131
     * @param \Symfony\Component\HttpFoundation\Request $request The current request.
132
     *
133
     * @return void
134
     */
135
    public function logout(Request $request): void
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

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

135
    public function logout(/** @scrutinizer ignore-unused */ Request $request): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
136
    {
137
        $authority = $this->moduleConfig->getValue('authority');
138
        $as = new $this->authSimple($authority);
139
140
        $returnURL = $this->moduleConfig->getValue('returnURL');
141
        $as->logout($returnURL);
142
    }
143
144
145
    /**
146
     * @param \Symfony\Component\HttpFoundation\Request $request The current request.
147
     *
148
     * @return \SimpleSAML\XHTML\Template
149
     */
150
    public function main(Request $request): Template
151
    {
152
        $hashAttributes = $this->moduleConfig->getOptionalValue('attributes.hash', false);
153
        $excludeAttributes = $this->moduleConfig->getOptionalValue('attributes.exclude', []);
154
155
        // Check if valid local session exists
156
        $authority = $this->moduleConfig->getValue('authority');
157
        $as = new $this->authSimple($authority);
158
        $as->requireAuth();
159
160
        // Get released attributes
161
        $attributes = $as->getAttributes();
162
163
        // Get metadata storage handler
164
        $metadata = $this->metadataStorageHandler;
165
166
        /*
167
         * Get IdP id and metadata
168
         */
169
        $idp_entityid = $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted');
170
        $idp_metadata = $metadata->getMetaData($idp_entityid, 'saml20-idp-hosted');
171
172
        // Calc correct source
173
        if ($as->getAuthData('saml:sp:IdP') !== null) {
174
            // from a remote idp (as bridge)
175
            $source = 'saml20-idp-remote|' . $as->getAuthData('saml:sp:IdP');
176
        } else {
177
            // from the local idp
178
            $source = $idp_metadata['metadata-set'] . '|' . $idp_entityid;
179
        }
180
181
        // Get user ID
182
        $userid_attributename = $this->moduleConfig->getOptionalString(
183
            'identifyingAttribute',
184
            'eduPersonPrincipalName',
185
        );
186
        $userids = $attributes[$userid_attributename];
187
188
        if (empty($userids)) {
189
            throw new Exception(sprintf(
190
                'Could not generate useridentifier for storing consent. Attribute [%s] was not available.',
191
                $userid_attributename,
192
            ));
193
        }
194
195
        $userid = $userids[0];
196
197
        // Get all SP metadata
198
        $all_sp_metadata = $metadata->getList('saml20-sp-remote');
199
200
        $sp_entityid = $request->query->get('cv');
201
        $action = $request->query->get('action');
202
203
        Logger::notice('consentAdmin: sp: ' . $sp_entityid . ' action: ' . $action);
204
205
        // Remove services, whitch have consent disabled
206
        if (isset($idp_metadata['consent.disable'])) {
207
            foreach ($idp_metadata['consent.disable'] as $disable) {
208
                if (array_key_exists($disable, $all_sp_metadata)) {
209
                    unset($all_sp_metadata[$disable]);
210
                }
211
            }
212
        }
213
214
        Logger::info('consentAdmin: ' . $idp_entityid);
215
216
        // Parse consent config
217
        $consent_storage = $this->store::parseStoreConfig($this->moduleConfig->getValue('consentadmin'));
218
219
        // Calc correct user ID hash
220
        $hashed_user_id = $this->consent::getHashedUserID($userid, $source);
221
222
        // If a checkbox have been clicked
223
        if ($action !== null && $sp_entityid !== null) {
224
            // init template to enable translation of status messages
225
            $template = new Template(
226
                $this->config,
227
                'consentAdmin:consentadminajax.twig',
228
                'consentAdmin:consentadmin',
0 ignored issues
show
Unused Code introduced by
The call to SimpleSAML\XHTML\Template::__construct() has too many arguments starting with 'consentAdmin:consentadmin'. ( Ignorable by Annotation )

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

228
            $template = /** @scrutinizer ignore-call */ new Template(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
229
            );
230
231
            // Get SP metadata
232
            $sp_metadata = $metadata->getMetaData($sp_entityid, 'saml20-sp-remote');
233
234
            // Run AuthProc filters
235
            list($targeted_id, $attribute_hash, $attributes) = $this->driveProcessingChain(
236
                $idp_metadata,
237
                $source,
238
                $sp_metadata,
239
                $sp_entityid,
240
                $attributes,
241
                $userid,
242
                $hashAttributes,
243
                $excludeAttributes,
244
            );
245
246
            // Add a consent (or update if attributes have changed and old consent for SP and IdP exists)
247
            if ($action == 'true') {
248
                $isStored = $consent_storage->saveConsent($hashed_user_id, $targeted_id, $attribute_hash);
249
            } else {
250
                if ($action == 'false') {
251
                    // Got consent, so this is a request to remove it
252
                    $consent_storage->deleteConsent($hashed_user_id, $targeted_id);
253
                    $isStored = false;
254
                } else {
255
                    Logger::info('consentAdmin: unknown action');
256
                    $isStored = null;
257
                }
258
            }
259
            $template->data['isStored'] = $isStored;
260
            return $template;
261
        }
262
263
        // Get all consents for user
264
        $user_consent_list = $consent_storage->getConsents($hashed_user_id);
265
266
        // Parse list of consents
267
        $user_consent = [];
268
        foreach ($user_consent_list as $c) {
269
            $user_consent[$c[0]] = $c[1];
270
        }
271
272
        // Init template
273
        $template = new Template($this->config, 'consentAdmin:consentadmin.twig', 'consentAdmin:consentadmin');
274
        $template->getLocalization()->addAttributeDomains();
275
276
        $sp_list = [];
277
278
        // Process consents for all SP
279
        foreach ($all_sp_metadata as $sp_entityid => $sp_values) {
280
            // Get metadata for SP
281
            $sp_metadata = $metadata->getMetaData($sp_entityid, 'saml20-sp-remote');
282
283
            // Run attribute filters
284
            list($targeted_id, $attribute_hash, $attributes) = $this->driveProcessingChain(
285
                $idp_metadata,
286
                $source,
287
                $sp_metadata,
288
                $sp_entityid,
289
                $attributes,
290
                $userid,
291
                $hashAttributes,
292
                $excludeAttributes,
293
            );
294
295
            // Check if consent exists
296
            if (array_key_exists($targeted_id, $user_consent)) {
297
                $sp_status = "changed";
298
                Logger::info('consentAdmin: changed');
299
                // Check if consent is valid. (Possible that attributes has changed)
300
                if ($user_consent[$targeted_id] == $attribute_hash) {
301
                    Logger::info('consentAdmin: ok');
302
                    $sp_status = "ok";
303
                }
304
                // Consent does not exist
305
            } else {
306
                Logger::info('consentAdmin: none');
307
                $sp_status = "none";
308
            }
309
310
            // Set description of SP
311
            $sp_description = null;
312
            if (!empty($sp_metadata['description']) && is_array($sp_metadata['description'])) {
313
                $sp_description = $sp_metadata['description'];
314
            }
315
316
            // Add a URL to the service if present in metadata
317
            $sp_service_url = isset($sp_metadata['ServiceURL']) ? $sp_metadata['ServiceURL'] : null;
318
319
            // Fill out array for the template
320
            $sp_list[$sp_entityid] = [
321
                'spentityid'       => $sp_entityid,
322
                'name'             => $template->getEntityDisplayName($sp_metadata),
323
                'description'      => $sp_description,
324
                'consentStatus'    => $sp_status,
325
                'consentValue'     => $sp_entityid,
326
                'attributes_by_sp' => $attributes,
327
                'serviceurl'       => $sp_service_url,
328
            ];
329
        }
330
331
        $template->data['header'] = 'Consent Administration';
332
        $template->data['spList'] = $sp_list;
333
        $template->data['showDescription'] = $this->moduleConfig->getBoolean('showDescription');
334
335
        return $template;
336
    }
337
338
339
    /**
340
     * Runs the processing chain and ignores all filter which have user
341
     * interaction.
342
     *
343
     * @param array $idp_metadata
344
     * @param string $source
345
     * @param array $sp_metadata
346
     * @param string $sp_entityid
347
     * @param array $attributes
348
     * @param string $userid
349
     * @param bool $hashAttributes
350
     * @param array $excludeAttributes
351
     * @return array
352
     */
353
    private function driveProcessingChain(
354
        array $idp_metadata,
355
        string $source,
356
        array $sp_metadata,
357
        string $sp_entityid,
358
        array $attributes,
359
        string $userid,
360
        bool $hashAttributes,
361
        array $excludeAttributes,
362
    ): array {
363
        /*
364
         * Create a new processing chain
365
         */
366
        $pc = new Auth\ProcessingChain($idp_metadata, $sp_metadata, 'idp');
367
368
        /*
369
         * Construct the state.
370
         * REMEMBER: Do not set Return URL if you are calling processStatePassive
371
         */
372
        $authProcState = [
373
            'Attributes'  => $attributes,
374
            'Destination' => $sp_metadata,
375
            'SPMetadata'  => $sp_metadata,
376
            'Source'      => $idp_metadata,
377
            'IdPMetadata' => $idp_metadata,
378
            'isPassive'   => true,
379
        ];
380
381
        /* we're being bridged, so add that info to the state */
382
        if (strpos($source, '-idp-remote|') !== false) {
383
            /** @var int $i */
384
            $i = strpos($source, '|');
385
            $authProcState['saml:sp:IdP'] = substr($source, $i + 1);
386
        }
387
388
        /*
389
         * Call processStatePAssive.
390
         * We are not interested in any user interaction, only modifications to the attributes
391
         */
392
        $pc->processStatePassive($authProcState);
393
394
        $attributes = $authProcState['Attributes'];
395
        // Remove attributes that do not require consent/should be excluded
396
        foreach ($attributes as $attrkey => $attrval) {
397
            if (in_array($attrkey, $excludeAttributes)) {
398
                unset($attributes[$attrkey]);
399
            }
400
        }
401
402
        /*
403
         * Generate identifiers and hashes
404
         */
405
        $destination = $sp_metadata['metadata-set'] . '|' . $sp_entityid;
406
407
        $targeted_id = $this->consent::getTargetedID($userid, $source, $destination);
408
        $attribute_hash = $this->consent::getAttributeHash($attributes, $hashAttributes);
409
410
        Logger::info('consentAdmin: user: ' . $userid);
411
        Logger::info('consentAdmin: target: ' . $targeted_id);
412
        Logger::info('consentAdmin: attribute: ' . $attribute_hash);
413
414
        // Return values
415
        return [$targeted_id, $attribute_hash, $attributes];
416
    }
417
}
418