Passed
Pull Request — master (#3)
by Tim
03:41 queued 59s
created

ConsentAdmin::setStore()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
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
/**
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\Configuration */
31
    protected Configuration $moduleConfig;
32
33
    /** @var \SimpleSAML\Session */
34
    protected Session $session;
35
36
    /** @var \SimpleSAML\Metadata\MetaDataStorageHandler */
37
    protected MetaDataStorageHandler $metadataStorageHandler;
38
39
    /**
40
     * @var \SimpleSAML\Auth\Simple|string
41
     * @psalm-var \SimpleSAML\Auth\Simple|class-string
42
     */
43
    protected $authSimple = Auth\Simple::class;
44
45
    /**
46
     * @var \SimpleSAML\Module\consent\Auth\Process\Consent|string
47
     * @psalm-var \SimpleSAML\Module\consent\Auth\Process\Consent|class-string
48
     */
49
    protected $consent = Consent::class;
50
51
    /**
52
     * @var \SimpleSAML\Module\consent\Store|string
53
     * @psalm-var \SimpleSAML\Module\consent\Store|class-string
54
     */
55
    protected $store = Store::class;
56
57
58
    /**
59
     * Controller constructor.
60
     *
61
     * It initializes the global configuration and session for the controllers implemented here.
62
     *
63
     * @param \SimpleSAML\Configuration $config The configuration to use by the controllers.
64
     * @param \SimpleSAML\Session $session The session to use by the controllers.
65
     *
66
     * @throws \Exception
67
     */
68
    public function __construct(
69
        Configuration $config,
70
        Session $session
71
    ) {
72
        $this->config = $config;
73
        $this->moduleConfig = Configuration::getConfig('module_consentAdmin.php');
74
        $this->metadataStorageHandler = MetaDataStorageHandler::getMetadataHandler();
75
        $this->session = $session;
76
    }
77
78
79
    /**
80
     * Inject the \SimpleSAML\Module\consent\Store dependency.
81
     *
82
     * @param \SimpleSAML\Module\consent\Store $store
83
     */
84
    public function setStore(Store $store): void
85
    {
86
        $this->store = $store;
87
    }
88
89
90
    /**
91
     * Inject the \SimpleSAML\Auth\Simple dependency.
92
     *
93
     * @param \SimpleSAML\Auth\Simple $authSimple
94
     */
95
    public function setAuthSimple(Auth\Simple $authSimple): void
96
    {
97
        $this->authSimple = $authSimple;
98
    }
99
100
101
    /**
102
     * Inject the \SimpleSAML\Metadata\MetaDataStorageHandler dependency.
103
     *
104
     * @param \SimpleSAML\Metadata\MetaDataStorageHandler $handler
105
     */
106
    public function setMetadataStorageHandler(MetadataStorageHandler $handler): void
107
    {
108
        $this->metadataStorageHandler = $handler;
109
    }
110
111
112
    /**
113
     * Inject the \SimpleSAML\Module\consent\Auth\Process\Consent dependency.
114
     *
115
     * @param \SimpleSAML\Module\consent\Auth\Process\Consent $consent
116
     */
117
    public function setConsent(Consent $consent): void
118
    {
119
        $this->consent = $consent;
120
    }
121
122
123
    /**
124
     * @param \Symfony\Component\HttpFoundation\Request $request The current request.
125
     *
126
     * @return \SimpleSAML\XHTML\Template
127
     */
128
    public function main(Request $request): Template
129
    {
130
        $authority = $this->moduleConfig->getValue('authority');
131
132
        $as = new $this->authSimple($authority);
133
134
        // If request is a logout request
135
        $logout = $request->get('logout');
136
        if ($logout !== null) {
137
            $returnURL = $this->moduleConfig->getValue('returnURL');
138
            $as->logout($returnURL);
139
        }
140
141
        $hashAttributes = $this->moduleConfig->getValue('attributes.hash', false);
142
143
        $excludeAttributes = $this->moduleConfig->getValue('attributes.exclude', []);
144
145
        // Check if valid local session exists
146
        $as->requireAuth();
147
148
        // Get released attributes
149
        $attributes = $as->getAttributes();
150
151
        // Get metadata storage handler
152
        $metadata = $this->metadataStorageHandler;
153
154
        /*
155
         * Get IdP id and metadata
156
         */
157
        $idp_entityid = $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted');
158
        $idp_metadata = $metadata->getMetaData($idp_entityid, 'saml20-idp-hosted');
159
160
        // Calc correct source
161
        if ($as->getAuthData('saml:sp:IdP') !== null) {
162
            // from a remote idp (as bridge)
163
            $source = 'saml20-idp-remote|' . $as->getAuthData('saml:sp:IdP');
164
        } else {
165
            // from the local idp
166
            $source = $idp_metadata['metadata-set'] . '|' . $idp_entityid;
167
        }
168
169
        // Get user ID
170
        if (isset($idp_metadata['userid.attribute']) && is_string($idp_metadata['userid.attribute'])) {
171
            $userid_attributename = $idp_metadata['userid.attribute'];
172
        } else {
173
            $userid_attributename = 'eduPersonPrincipalName';
174
        }
175
176
        $userids = $attributes[$userid_attributename];
177
178
        if (empty($userids)) {
179
            throw new Exception(sprintf(
180
                'Could not generate useridentifier for storing consent. Attribute [%s] was not available.',
181
                $userid_attributename
182
            ));
183
        }
184
185
        $userid = $userids[0];
186
187
        // Get all SP metadata
188
        $all_sp_metadata = $metadata->getList('saml20-sp-remote');
189
190
        $sp_entityid = $request->get('cv');;
191
        $action = $request->get('action');
192
193
        Logger::critical('consentAdmin: sp: ' . $sp_entityid . ' action: ' . $action);
194
195
        // Remove services, whitch have consent disabled
196
        if (isset($idp_metadata['consent.disable'])) {
197
            foreach ($idp_metadata['consent.disable'] as $disable) {
198
                if (array_key_exists($disable, $all_sp_metadata)) {
199
                    unset($all_sp_metadata[$disable]);
200
                }
201
            }
202
        }
203
204
        Logger::info('consentAdmin: ' . $idp_entityid);
205
206
        // Parse consent config
207
        $consent_storage = $this->store::parseStoreConfig($this->moduleConfig->getValue('consentadmin'));
208
209
        // Calc correct user ID hash
210
        $hashed_user_id = $this->consent::getHashedUserID($userid, $source);
211
212
        // If a checkbox have been clicked
213
        if ($action !== null && $sp_entityid !== null) {
214
            // init template to enable translation of status messages
215
            $template = new Template(
216
                $this->config,
217
                'consentAdmin:consentadminajax.twig',
218
                'consentAdmin:consentadmin'
219
            );
220
221
            // Get SP metadata
222
            $sp_metadata = $metadata->getMetaData($sp_entityid, 'saml20-sp-remote');
223
224
            // Run AuthProc filters
225
            list($targeted_id, $attribute_hash, $attributes) = $this->driveProcessingChain(
226
                $idp_metadata,
227
                $source,
228
                $sp_metadata,
229
                $sp_entityid,
230
                $attributes,
231
                $userid,
232
                $hashAttributes,
233
                $excludeAttributes
234
            );
235
236
            // Add a consent (or update if attributes have changed and old consent for SP and IdP exists)
237
            if ($action == 'true') {
238
                $isStored = $consent_storage->saveConsent($hashed_user_id, $targeted_id, $attribute_hash);
239
            } else {
240
                if ($action == 'false') {
241
                    // Got consent, so this is a request to remove it
242
                    $consent_storage->deleteConsent($hashed_user_id, $targeted_id);
243
                    $isStored = false;
244
                } else {
245
                    Logger::info('consentAdmin: unknown action');
246
                    $isStored = null;
247
                }
248
            }
249
            $template->data['isStored'] = $isStored;
250
            return $template;
251
        }
252
253
        // Get all consents for user
254
        $user_consent_list = $consent_storage->getConsents($hashed_user_id);
255
256
        // Parse list of consents
257
        $user_consent = [];
258
        foreach ($user_consent_list as $c) {
259
            $user_consent[$c[0]] = $c[1];
260
        }
261
262
        // Init template
263
        $template = new Template($this->config, 'consentAdmin:consentadmin.twig', 'consentAdmin:consentadmin');
264
        $template->getLocalization()->addAttributeDomains();
265
266
        $sp_list = [];
267
268
        // Process consents for all SP
269
        foreach ($all_sp_metadata as $sp_entityid => $sp_values) {
270
            // Get metadata for SP
271
            $sp_metadata = $metadata->getMetaData($sp_entityid, 'saml20-sp-remote');
272
273
            // Run attribute filters
274
            list($targeted_id, $attribute_hash, $attributes) = $this->driveProcessingChain(
275
                $idp_metadata,
276
                $source,
277
                $sp_metadata,
278
                $sp_entityid,
279
                $attributes,
280
                $userid,
281
                $hashAttributes,
282
                $excludeAttributes
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
            } elseif (isset($sp_values['name']) && is_string($sp_values['name'])) {
304
                $sp_name = $sp_metadata['name'];
305
            } elseif (isset($sp_values['OrganizationDisplayName']) && is_array($sp_values['OrganizationDisplayName'])) {
306
                $sp_name = $sp_metadata['OrganizationDisplayName'];
307
            } else {
308
                $sp_name = $sp_entityid;
309
            }
310
311
            // Set description of SP
312
            $sp_description = null;
313
            if (!empty($sp_metadata['description']) && is_array($sp_metadata['description'])) {
314
                $sp_description = $sp_metadata['description'];
315
            }
316
317
            // Add a URL to the service if present in metadata
318
            $sp_service_url = isset($sp_metadata['ServiceURL']) ? $sp_metadata['ServiceURL'] : null;
319
320
            // Fill out array for the template
321
            $sp_list[$sp_entityid] = [
322
                'spentityid'       => $sp_entityid,
323
                'name'             => $sp_name,
324
                'description'      => $sp_description,
325
                'consentStatus'    => $sp_status,
326
                'consentValue'     => $sp_entityid,
327
                'attributes_by_sp' => $attributes,
328
                'serviceurl'       => $sp_service_url,
329
            ];
330
        }
331
332
        $template->data['header'] = 'Consent Administration';
333
        $template->data['spList'] = $sp_list;
334
        $template->data['showDescription'] = $this->moduleConfig->getValue('showDescription');
335
336
        return $template;
337
    }
338
339
340
    /**
341
     * Runs the processing chain and ignores all filter which have user
342
     * interaction.
343
     *
344
     * @param array $idp_metadata
345
     * @param string $source
346
     * @param array $sp_metadata
347
     * @param string $sp_entityid
348
     * @param array $attributes
349
     * @param string $userid
350
     * @param bool $hashAttributes
351
     * @param array $excludeAttributes
352
     * @return array
353
     */
354
    private function driveProcessingChain(
355
        array $idp_metadata,
356
        string $source,
357
        array $sp_metadata,
358
        string $sp_entityid,
359
        array $attributes,
360
        string $userid,
361
        bool $hashAttributes,
362
        array $excludeAttributes
363
    ): array {
364
        /*
365
         * Create a new processing chain
366
         */
367
        $pc = new Auth\ProcessingChain($idp_metadata, $sp_metadata, 'idp');
368
369
        /*
370
         * Construct the state.
371
         * REMEMBER: Do not set Return URL if you are calling processStatePassive
372
         */
373
        $authProcState = [
374
            'Attributes'  => $attributes,
375
            'Destination' => $sp_metadata,
376
            'SPMetadata'  => $sp_metadata,
377
            'Source'      => $idp_metadata,
378
            'IdPMetadata' => $idp_metadata,
379
            'isPassive'   => true,
380
        ];
381
382
        /* we're being bridged, so add that info to the state */
383
        if (strpos($source, '-idp-remote|') !== false) {
384
            /** @var int $i */
385
            $i = strpos($source, '|');
386
            $authProcState['saml:sp:IdP'] = substr($source, $i + 1);
387
        }
388
389
        /*
390
         * Call processStatePAssive.
391
         * We are not interested in any user interaction, only modifications to the attributes
392
         */
393
        $pc->processStatePassive($authProcState);
394
395
        $attributes = $authProcState['Attributes'];
396
        // Remove attributes that do not require consent/should be excluded
397
        foreach ($attributes as $attrkey => $attrval) {
398
            if (in_array($attrkey, $excludeAttributes)) {
399
                unset($attributes[$attrkey]);
400
            }
401
        }
402
403
        /*
404
         * Generate identifiers and hashes
405
         */
406
        $destination = $sp_metadata['metadata-set'] . '|' . $sp_entityid;
407
408
        $targeted_id = $this->consent::getTargetedID($userid, $source, $destination);
409
        $attribute_hash = $this->consent::getAttributeHash($attributes, $hashAttributes);
410
411
        Logger::info('consentAdmin: user: ' . $userid);
412
        Logger::info('consentAdmin: target: ' . $targeted_id);
413
        Logger::info('consentAdmin: attribute: ' . $attribute_hash);
414
415
        // Return values
416
        return [$targeted_id, $attribute_hash, $attributes];
417
    }
418
}
419