Passed
Pull Request — master (#14)
by Tim
03:40 queued 01:40
created

ConsentController   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 289
Duplicated Lines 0 %

Importance

Changes 11
Bugs 0 Features 0
Metric Value
eloc 159
c 11
b 0
f 0
dl 0
loc 289
rs 8.72
wmc 46

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
F getconsent() 0 168 31
B noconsent() 0 51 10
A logoutcompleted() 0 3 1
A logout() 0 15 3

How to fix   Complexity   

Complex Class

Complex classes like ConsentController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ConsentController, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Module\consent\Controller;
6
7
use Exception;
8
use SimpleSAML\Auth;
9
use SimpleSAML\Configuration;
10
use SimpleSAML\Error;
11
use SimpleSAML\HTTP\RunnableResponse;
12
use SimpleSAML\IdP;
13
use SimpleSAML\Locale\Translate;
14
use SimpleSAML\Logger;
15
use SimpleSAML\Module;
16
use SimpleSAML\Session;
17
use SimpleSAML\Stats;
18
use SimpleSAML\Utils;
19
use SimpleSAML\XHTML\Template;
20
use Symfony\Component\HttpFoundation\Request;
21
22
/**
23
 * Controller class for the consent module.
24
 *
25
 * This class serves the consent views available in the module.
26
 *
27
 * @package SimpleSAML\Module\consent
28
 */
29
class ConsentController
30
{
31
    /** @var \SimpleSAML\Configuration */
32
    protected $config;
33
34
    /** @var \SimpleSAML\Session */
35
    protected $session;
36
37
38
    /**
39
     * ConsentController constructor.
40
     *
41
     * @param \SimpleSAML\Configuration $config The configuration to use.
42
     * @param \SimpleSAML\Session $session The current user session.
43
     */
44
    public function __construct(Configuration $config, Session $session)
45
    {
46
        $this->config = $config;
47
        $this->session = $session;
48
    }
49
50
51
52
    /**
53
     * Display consent form.
54
     *
55
     * @param \Symfony\Component\HttpFoundation\Request $request The current request.
56
     *
57
     * @return \SimpleSAML\XHTML\Template
58
     */
59
    public function getconsent(Request $request): Template
60
    {
61
        session_cache_limiter('nocache');
62
63
        Logger::info('Consent - getconsent: Accessing consent interface');
64
65
        $stateId = $request->query->get('StateId');
66
        if ($stateId === null) {
67
            throw new Error\BadRequest('Missing required StateId query parameter.');
68
        }
69
70
        $state = Auth\State::loadState($stateId, 'consent:request');
71
72
        if (is_null($state)) {
73
            throw new Error\NoState();
74
        } elseif (array_key_exists('core:SP', $state)) {
75
            $spentityid = $state['core:SP'];
76
        } elseif (array_key_exists('saml:sp:State', $state)) {
77
            $spentityid = $state['saml:sp:State']['core:SP'];
78
        } else {
79
            $spentityid = 'UNKNOWN';
80
        }
81
82
        // The user has pressed the yes-button
83
        if ($request->query->get('yes') !== null) {
84
            if ($request->query->get('saveconsent') !== null) {
85
                Logger::stats('consentResponse remember');
86
            } else {
87
                Logger::stats('consentResponse rememberNot');
88
            }
89
90
            $statsInfo = [
91
                'remember' => $request->query->get('saveconsent'),
92
            ];
93
            if (isset($state['Destination']['entityid'])) {
94
                $statsInfo['spEntityID'] = $state['Destination']['entityid'];
95
            }
96
            Stats::log('consent:accept', $statsInfo);
97
98
            if (
99
                array_key_exists('consent:store', $state)
100
                && $request->query->get('saveconsent') === '1'
101
            ) {
102
                // Save consent
103
                $store = $state['consent:store'];
104
                $userId = $state['consent:store.userId'];
105
                $targetedId = $state['consent:store.destination'];
106
                $attributeSet = $state['consent:store.attributeSet'];
107
108
                Logger::debug(
109
                    'Consent - saveConsent() : [' . $userId . '|' . $targetedId . '|' . $attributeSet . ']'
110
                );
111
                try {
112
                    $store->saveConsent($userId, $targetedId, $attributeSet);
113
                } catch (Exception $e) {
114
                    Logger::error('Consent: Error writing to storage: ' . $e->getMessage());
115
                }
116
            }
117
118
            Auth\ProcessingChain::resumeProcessing($state);
119
        }
120
121
        // Prepare attributes for presentation
122
        $attributes = $state['Attributes'];
123
        $noconsentattributes = $state['consent:noconsentattributes'];
124
125
        // Remove attributes that do not require consent
126
        foreach ($attributes as $attrkey => $attrval) {
127
            if (in_array($attrkey, $noconsentattributes, true)) {
128
                unset($attributes[$attrkey]);
129
            }
130
        }
131
        $para = [
132
            'attributes' => &$attributes
133
        ];
134
135
        // Reorder attributes according to attributepresentation hooks
136
        Module::callHooks('attributepresentation', $para);
137
138
        // Parse parameters
139
        if (array_key_exists('name', $state['Source'])) {
140
            $srcName = $state['Source']['name'];
141
        } elseif (array_key_exists('OrganizationDisplayName', $state['Source'])) {
142
            $srcName = $state['Source']['OrganizationDisplayName'];
143
        } else {
144
            $srcName = $state['Source']['entityid'];
145
        }
146
147
        if (array_key_exists('name', $state['Destination'])) {
148
            $dstName = $state['Destination']['name'];
149
        } elseif (array_key_exists('OrganizationDisplayName', $state['Destination'])) {
150
            $dstName = $state['Destination']['OrganizationDisplayName'];
151
        } else {
152
            $dstName = $state['Destination']['entityid'];
153
        }
154
155
        // Make, populate and layout consent form
156
        $t = new Template($this->config, 'consent:consentform.twig');
157
        $translator = $t->getTranslator();
158
        $t->data['srcMetadata'] = $state['Source'];
159
        $t->data['dstMetadata'] = $state['Destination'];
160
        $t->data['yesTarget'] = Module::getModuleURL('consent/getconsent');
161
        $t->data['yesData'] = ['StateId' => $stateId];
162
        $t->data['noTarget'] = Module::getModuleURL('consent/noconsent');
163
        $t->data['noData'] = ['StateId' => $stateId];
164
        $t->data['attributes'] = $attributes;
165
        $t->data['checked'] = $state['consent:checked'];
166
        $t->data['stateId'] = $stateId;
167
168
        $t->data['srcName'] = htmlspecialchars(is_array($srcName) ? $translator->getPreferredTranslation($srcName) : $srcName);
169
        $t->data['dstName'] = htmlspecialchars(is_array($dstName) ? $translator->getPreferredTranslation($dstName) : $dstName);
170
171
        if (array_key_exists('descr_purpose', $state['Destination'])) {
172
            $t->data['dstDesc'] = $translator->getPreferredTranslation(
173
                Utils\Arrays::arrayize(
174
                    $state['Destination']['descr_purpose'],
175
                    'en'
176
                )
177
            );
178
        }
179
180
        // Fetch privacypolicy
181
        if (
182
            array_key_exists('UIInfo', $state['Destination']) &&
183
            array_key_exists('PrivacyStatementURL', $state['Destination']['UIInfo']) &&
184
            (!empty($state['Destination']['UIInfo']['PrivacyStatementURL']))
185
        ) {
186
            $privacypolicy = reset($state['Destination']['UIInfo']['PrivacyStatementURL']);
187
        } elseif (
188
            array_key_exists('UIInfo', $state['Source']) &&
189
            array_key_exists('PrivacyStatementURL', $state['Source']['UIInfo']) &&
190
            (!empty($state['Source']['UIInfo']['PrivacyStatementURL']))
191
        ) {
192
            $privacypolicy = reset($state['Source']['UIInfo']['PrivacyStatementURL']);
193
        } else {
194
            $privacypolicy = false;
195
        }
196
        if ($privacypolicy !== false) {
197
            $privacypolicy = str_replace(
198
                '%SPENTITYID%',
199
                urlencode($spentityid),
200
                $privacypolicy
201
            );
202
        }
203
        $t->data['sppp'] = $privacypolicy;
204
205
        // Set focus element
206
        switch ($state['consent:focus']) {
207
            case 'yes':
208
                $t->data['autofocus'] = 'yesbutton';
209
                break;
210
            case 'no':
211
                $t->data['autofocus'] = 'nobutton';
212
                break;
213
            case null:
214
            default:
215
                break;
216
        }
217
218
        $t->data['usestorage'] = array_key_exists('consent:store', $state);
219
220
        if (array_key_exists('consent:hiddenAttributes', $state)) {
221
            $t->data['hiddenAttributes'] = $state['consent:hiddenAttributes'];
222
        } else {
223
            $t->data['hiddenAttributes'] = [];
224
        }
225
226
        return $t;
227
    }
228
229
230
    /**
231
     * @param \Symfony\Component\HttpFoundation\Request $request The current request.
232
     *
233
     * @return \SimpleSAML\XHTML\Template
234
     */
235
    public function noconsent(Request $request): Template
236
    {
237
        $stateId = $request->query->get('StateId');
238
        if ($stateId === null) {
239
            throw new Error\BadRequest('Missing required StateId query parameter.');
240
        }
241
242
        $state = Auth\State::loadState($stateId, 'consent:request');
243
        if (is_null($state)) {
244
            throw new Error\NoState();
245
        }
246
247
        $resumeFrom = Module::getModuleURL(
248
            'consent/getconsent',
249
            ['StateId' => $stateId]
250
        );
251
252
        $logoutLink = Module::getModuleURL(
253
            'consent/logout',
254
            ['StateId' => $stateId]
255
        );
256
257
        $aboutService = null;
258
        if (!isset($state['consent:showNoConsentAboutService']) || $state['consent:showNoConsentAboutService']) {
259
            if (isset($state['Destination']['url.about'])) {
260
                $aboutService = $state['Destination']['url.about'];
261
            }
262
        }
263
264
        $statsInfo = [];
265
        if (isset($state['Destination']['entityid'])) {
266
            $statsInfo['spEntityID'] = $state['Destination']['entityid'];
267
        }
268
        Stats::log('consent:reject', $statsInfo);
269
270
        if (array_key_exists('name', $state['Destination'])) {
271
            $dstName = $state['Destination']['name'];
272
        } elseif (array_key_exists('OrganizationDisplayName', $state['Destination'])) {
273
            $dstName = $state['Destination']['OrganizationDisplayName'];
274
        } else {
275
            $dstName = $state['Destination']['entityid'];
276
        }
277
278
        $t = new Template($this->config, 'consent:noconsent.twig');
279
        $translator = $t->getTranslator();
280
        $t->data['dstMetadata'] = $state['Destination'];
281
        $t->data['resumeFrom'] = $resumeFrom;
282
        $t->data['aboutService'] = $aboutService;
283
        $t->data['logoutLink'] = $logoutLink;
284
        $t->data['dstName'] = htmlspecialchars(is_array($dstName) ? $translator->getPreferredTranslation($dstName) : $dstName);
285
        return $t;
286
    }
287
288
289
    /**
290
     * @param \Symfony\Component\HttpFoundation\Request $request The current request.
291
     *
292
     * @return \SimpleSAML\HTTP\RunnableResponse
293
     */
294
    public function logout(Request $request): RunnableResponse
295
    {
296
        $stateId = $request->query->get('StateId', null);
297
        if ($stateId === null) {
298
            throw new Error\BadRequest('Missing required StateId query parameter.');
299
        }
300
301
        $state = Auth\State::loadState($stateId, 'consent:request');
302
        if (is_null($state)) {
303
            throw new Error\NoState();
304
        }
305
        $state['Responder'] = ['\SimpleSAML\Module\consent\Logout', 'postLogout'];
306
307
        $idp = IdP::getByState($state);
308
        return new RunnableResponse([$idp, 'handleLogoutRequest'], [&$state, $stateId]);
309
    }
310
311
312
    /**
313
     * @return \SimpleSAML\XHTML\Template
314
     */
315
    public function logoutcompleted(): Template
316
    {
317
        return new Template($this->config, 'consent:logout_completed.twig');
318
    }
319
}
320