Passed
Pull Request — master (#14)
by Tim
02:20
created

ConsentController::logoutcompleted()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
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->get('StateId', false);
66
        if ($stateId === false) {
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 (array_key_exists('yes', $_REQUEST)) {
84
            if (array_key_exists('saveconsent', $_REQUEST)) {
85
                Logger::stats('consentResponse remember');
86
            } else {
87
                Logger::stats('consentResponse rememberNot');
88
            }
89
90
            $statsInfo = [
91
                'remember' => array_key_exists('saveconsent', $_REQUEST),
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
                && array_key_exists('saveconsent', $_REQUEST)
101
                && $_REQUEST['saveconsent'] === '1'
102
            ) {
103
                // Save consent
104
                $store = $state['consent:store'];
105
                $userId = $state['consent:store.userId'];
106
                $targetedId = $state['consent:store.destination'];
107
                $attributeSet = $state['consent:store.attributeSet'];
108
109
                Logger::debug(
110
                    'Consent - saveConsent() : [' . $userId . '|' . $targetedId . '|' . $attributeSet . ']'
111
                );
112
                try {
113
                    $store->saveConsent($userId, $targetedId, $attributeSet);
114
                } catch (Exception $e) {
115
                    Logger::error('Consent: Error writing to storage: ' . $e->getMessage());
116
                }
117
            }
118
119
            Auth\ProcessingChain::resumeProcessing($state);
120
        }
121
122
        // Prepare attributes for presentation
123
        $attributes = $state['Attributes'];
124
        $noconsentattributes = $state['consent:noconsentattributes'];
125
126
        // Remove attributes that do not require consent
127
        foreach ($attributes as $attrkey => $attrval) {
128
            if (in_array($attrkey, $noconsentattributes, true)) {
129
                unset($attributes[$attrkey]);
130
            }
131
        }
132
        $para = [
133
            'attributes' => &$attributes
134
        ];
135
136
        // Reorder attributes according to attributepresentation hooks
137
        Module::callHooks('attributepresentation', $para);
138
139
        // Parse parameters
140
        if (array_key_exists('name', $state['Source'])) {
141
            $srcName = $state['Source']['name'];
142
        } elseif (array_key_exists('OrganizationDisplayName', $state['Source'])) {
143
            $srcName = $state['Source']['OrganizationDisplayName'];
144
        } else {
145
            $srcName = $state['Source']['entityid'];
146
        }
147
148
        if (array_key_exists('name', $state['Destination'])) {
149
            $dstName = $state['Destination']['name'];
150
        } elseif (array_key_exists('OrganizationDisplayName', $state['Destination'])) {
151
            $dstName = $state['Destination']['OrganizationDisplayName'];
152
        } else {
153
            $dstName = $state['Destination']['entityid'];
154
        }
155
156
        // Make, populate and layout consent form
157
        $t = new Template($this->config, 'consent:consentform.twig');
158
        $translator = $t->getTranslator();
159
        $t->data['srcMetadata'] = $state['Source'];
160
        $t->data['dstMetadata'] = $state['Destination'];
161
        $t->data['yesTarget'] = Module::getModuleURL('consent/getconsent');
162
        $t->data['yesData'] = ['StateId' => $stateId];
163
        $t->data['noTarget'] = Module::getModuleURL('consent/noconsent');
164
        $t->data['noData'] = ['StateId' => $stateId];
165
        $t->data['attributes'] = $attributes;
166
        $t->data['checked'] = $state['consent:checked'];
167
        $t->data['stateId'] = $stateId;
168
169
        $t->data['srcName'] = htmlspecialchars(is_array($srcName) ? $translator->getPreferredTranslation($srcName) : $srcName);
170
        $t->data['dstName'] = htmlspecialchars(is_array($dstName) ? $translator->getPreferredTranslation($dstName) : $dstName);
171
172
        if (array_key_exists('descr_purpose', $state['Destination'])) {
173
            $t->data['dstDesc'] = $translator->getPreferredTranslation(
174
                Utils\Arrays::arrayize(
175
                    $state['Destination']['descr_purpose'],
176
                    'en'
177
                )
178
            );
179
        }
180
181
        // Fetch privacypolicy
182
        if (
183
            array_key_exists('UIInfo', $state['Destination']) &&
184
            array_key_exists('PrivacyStatementURL', $state['Destination']['UIInfo']) &&
185
            (!empty($state['Destination']['UIInfo']['PrivacyStatementURL']))
186
        ) {
187
            $privacypolicy = reset($state['Destination']['UIInfo']['PrivacyStatementURL']);
188
        } elseif (
189
            array_key_exists('UIInfo', $state['Source']) &&
190
            array_key_exists('PrivacyStatementURL', $state['Source']['UIInfo']) &&
191
            (!empty($state['Source']['UIInfo']['PrivacyStatementURL']))
192
        ) {
193
            $privacypolicy = reset($state['Source']['UIInfo']['PrivacyStatementURL']);
194
        } else {
195
            $privacypolicy = false;
196
        }
197
        if ($privacypolicy !== false) {
198
            $privacypolicy = str_replace(
199
                '%SPENTITYID%',
200
                urlencode($spentityid),
201
                $privacypolicy
202
            );
203
        }
204
        $t->data['sppp'] = $privacypolicy;
205
206
        // Set focus element
207
        switch ($state['consent:focus']) {
208
            case 'yes':
209
                $t->data['autofocus'] = 'yesbutton';
210
                break;
211
            case 'no':
212
                $t->data['autofocus'] = 'nobutton';
213
                break;
214
            case null:
215
            default:
216
                break;
217
        }
218
219
        $t->data['usestorage'] = array_key_exists('consent:store', $state);
220
221
        if (array_key_exists('consent:hiddenAttributes', $state)) {
222
            $t->data['hiddenAttributes'] = $state['consent:hiddenAttributes'];
223
        } else {
224
            $t->data['hiddenAttributes'] = [];
225
        }
226
227
        return $t;
228
    }
229
230
231
    /**
232
     * @param \Symfony\Component\HttpFoundation\Request $request The current request.
233
     *
234
     * @return \SimpleSAML\XHTML\Template
235
     */
236
    public function noconsent(Request $request): Template
237
    {
238
        $stateId = $request->get('StateId', false);
239
        if ($stateId === false) {
240
            throw new Error\BadRequest('Missing required StateId query parameter.');
241
        }
242
243
        /** @psalm-var array $state */
244
        $state = Auth\State::loadState($stateId, 'consent:request');
245
246
        $resumeFrom = Module::getModuleURL(
247
            'consent/getconsent',
248
            ['StateId' => $stateId]
249
        );
250
251
        $logoutLink = Module::getModuleURL(
252
            'consent/logout',
253
            ['StateId' => $stateId]
254
        );
255
256
        $aboutService = null;
257
        if (!isset($state['consent:showNoConsentAboutService']) || $state['consent:showNoConsentAboutService']) {
258
            if (isset($state['Destination']['url.about'])) {
259
                $aboutService = $state['Destination']['url.about'];
260
            }
261
        }
262
263
        $statsInfo = [];
264
        if (isset($state['Destination']['entityid'])) {
265
            $statsInfo['spEntityID'] = $state['Destination']['entityid'];
266
        }
267
        Stats::log('consent:reject', $statsInfo);
268
269
        if (array_key_exists('name', $state['Destination'])) {
270
            $dstName = $state['Destination']['name'];
271
        } elseif (array_key_exists('OrganizationDisplayName', $state['Destination'])) {
272
            $dstName = $state['Destination']['OrganizationDisplayName'];
273
        } else {
274
            $dstName = $state['Destination']['entityid'];
275
        }
276
277
        $t = new Template($this->config, 'consent:noconsent.twig');
278
        $translator = $t->getTranslator();
279
        $t->data['dstMetadata'] = $state['Destination'];
280
        $t->data['resumeFrom'] = $resumeFrom;
281
        $t->data['aboutService'] = $aboutService;
282
        $t->data['logoutLink'] = $logoutLink;
283
        $t->data['dstName'] = htmlspecialchars(is_array($dstName) ? $translator->getPreferredTranslation($dstName) : $dstName);
284
        return $t;
285
    }
286
287
288
    /**
289
     * @param \Symfony\Component\HttpFoundation\Request $request The current request.
290
     *
291
     * @return \SimpleSAML\HTTP\RunnableResponse
292
     */
293
    public function logout(Request $request): RunnableResponse
294
    {
295
        $stateId = $request->get('StateId', false);
296
        if ($stateId === false) {
297
            throw new Error\BadRequest('Missing required StateId query parameter.');
298
        }
299
300
        $state = Auth\State::loadState($stateId, 'consent:request');
301
302
        $state['Responder'] = ['\SimpleSAML\Module\consent\Logout', 'postLogout'];
303
304
        $idp = IdP::getByState($state);
0 ignored issues
show
Bug introduced by
It seems like $state can also be of type null; however, parameter $state of SimpleSAML\IdP::getByState() does only seem to accept array, 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

304
        $idp = IdP::getByState(/** @scrutinizer ignore-type */ $state);
Loading history...
305
        return new RunnableResponse([$idp, 'handleLogoutRequest'], [$state, null]);
306
    }
307
308
309
    /**
310
     * @param \Symfony\Component\HttpFoundation\Request $request The current request.
311
     *
312
     * @return \SimpleSAML\XHTML\Template
313
     */
314
    public function logoutcompleted(Request $request): Template
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

314
    public function logoutcompleted(/** @scrutinizer ignore-unused */ Request $request): Template

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...
315
    {
316
        return new Template($this->config, 'consent:logout_completed.twig');
317
    }
318
}
319