Passed
Push — master ( 452cd3...08361e )
by Tim
02:33
created

ConsentController::setLogger()   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
     * @var \SimpleSAML\Auth\State|string
39
     * @psalm-var \SimpleSAML\Auth\State|class-string
40
     */
41
    protected $authState = Auth\State::class;
42
43
    /**
44
     * @var \SimpleSAML\Logger|string
45
     * @psalm-var \SimpleSAML\Logger|class-string
46
     */
47
    protected $logger = Logger::class;
48
49
50
    /**
51
     * ConsentController constructor.
52
     *
53
     * @param \SimpleSAML\Configuration $config The configuration to use.
54
     * @param \SimpleSAML\Session $session The current user session.
55
     */
56
    public function __construct(Configuration $config, Session $session)
57
    {
58
        $this->config = $config;
59
        $this->session = $session;
60
    }
61
62
63
    /**
64
     * Inject the \SimpleSAML\Auth\State dependency.
65
     *
66
     * @param \SimpleSAML\Auth\State $authState
67
     */
68
    public function setAuthState(Auth\State $authState): void
69
    {
70
        $this->authState = $authState;
71
    }
72
73
74
    /**
75
     * Inject the \SimpleSAML\Logger dependency.
76
     *
77
     * @param \SimpleSAML\Logger $logger
78
     */
79
    public function setLogger(Logger $logger): void
80
    {
81
        $this->logger = $logger;
82
    }
83
84
85
    /**
86
     * Display consent form.
87
     *
88
     * @param \Symfony\Component\HttpFoundation\Request $request The current request.
89
     *
90
     * @return \SimpleSAML\XHTML\Template|\SimpleSAML\HTTP\RunnableResponse
91
     */
92
    public function getconsent(Request $request)
93
    {
94
        $this->logger::info('Consent - getconsent: Accessing consent interface');
95
96
        $stateId = $request->query->get('StateId');
97
        if ($stateId === null) {
98
            throw new Error\BadRequest('Missing required StateId query parameter.');
99
        }
100
101
        $state = $this->authState::loadState($stateId, 'consent:request');
102
103
        if (is_null($state)) {
104
            throw new Error\NoState();
105
        } elseif (array_key_exists('core:SP', $state)) {
106
            $spentityid = $state['core:SP'];
107
        } elseif (array_key_exists('saml:sp:State', $state)) {
108
            $spentityid = $state['saml:sp:State']['core:SP'];
109
        } else {
110
            $spentityid = 'UNKNOWN';
111
        }
112
113
        // The user has pressed the yes-button
114
        if ($request->query->get('yes') !== null) {
115
            if ($request->query->get('saveconsent') !== null) {
116
                $this->logger::stats('consentResponse remember');
117
            } else {
118
                $this->logger::stats('consentResponse rememberNot');
119
            }
120
121
            $statsInfo = [
122
                'remember' => $request->query->get('saveconsent'),
123
            ];
124
            if (isset($state['Destination']['entityid'])) {
125
                $statsInfo['spEntityID'] = $state['Destination']['entityid'];
126
            }
127
            Stats::log('consent:accept', $statsInfo);
128
129
            if (
130
                array_key_exists('consent:store', $state)
131
                && $request->query->get('saveconsent') === '1'
132
            ) {
133
                // Save consent
134
                $store = $state['consent:store'];
135
                $userId = $state['consent:store.userId'];
136
                $targetedId = $state['consent:store.destination'];
137
                $attributeSet = $state['consent:store.attributeSet'];
138
139
                $this->logger::debug(
140
                    'Consent - saveConsent() : [' . $userId . '|' . $targetedId . '|' . $attributeSet . ']'
141
                );
142
                try {
143
                    $store->saveConsent($userId, $targetedId, $attributeSet);
144
                } catch (Exception $e) {
145
                    $this->logger::error('Consent: Error writing to storage: ' . $e->getMessage());
146
                }
147
            }
148
149
            return new RunnableResponse([Auth\ProcessingChain::class, 'resumeProcessing'], [$state]);
150
        }
151
152
        // Prepare attributes for presentation
153
        $attributes = $state['Attributes'];
154
        $noconsentattributes = $state['consent:noconsentattributes'];
155
156
        // Remove attributes that do not require consent
157
        foreach ($attributes as $attrkey => $attrval) {
158
            if (in_array($attrkey, $noconsentattributes, true)) {
159
                unset($attributes[$attrkey]);
160
            }
161
        }
162
        $para = [
163
            'attributes' => &$attributes
164
        ];
165
166
        // Reorder attributes according to attributepresentation hooks
167
        Module::callHooks('attributepresentation', $para);
168
169
        // Unset the values for attributes that need to be hidden
170
        if (array_key_exists('consent:hiddenAttributes', $state)) {
171
            foreach ($state['consent:hiddenAttributes'] as $hidden) {
172
                if (array_key_exists($hidden, $attributes)) {
173
                    $attributes[$hidden] = null;
174
                }
175
            }
176
        }
177
178
        // Make, populate and layout consent form
179
        $t = new Template($this->config, 'consent:consentform.twig');
180
        $translator = $t->getTranslator();
0 ignored issues
show
Unused Code introduced by
The assignment to $translator is dead and can be removed.
Loading history...
181
        $t->data['attributes'] = $attributes;
182
        $t->data['checked'] = $state['consent:checked'];
183
        $t->data['stateId'] = $stateId;
184
        $t->data['source'] = $state['Source'];
185
        $t->data['destination'] = $state['Destination'];
186
187
        // Fetch privacy policy
188
        if (
189
            array_key_exists('UIInfo', $state['Destination']) &&
190
            array_key_exists('PrivacyStatementURL', $state['Destination']['UIInfo']) &&
191
            (!empty($state['Destination']['UIInfo']['PrivacyStatementURL']))
192
        ) {
193
            $privacypolicy = reset($state['Destination']['UIInfo']['PrivacyStatementURL']);
194
        } elseif (
195
            array_key_exists('UIInfo', $state['Source']) &&
196
            array_key_exists('PrivacyStatementURL', $state['Source']['UIInfo']) &&
197
            (!empty($state['Source']['UIInfo']['PrivacyStatementURL']))
198
        ) {
199
            $privacypolicy = reset($state['Source']['UIInfo']['PrivacyStatementURL']);
200
        } else {
201
            $privacypolicy = false;
202
        }
203
        if ($privacypolicy !== false) {
204
            $privacypolicy = str_replace(
205
                '%SPENTITYID%',
206
                urlencode($spentityid),
207
                $privacypolicy
208
            );
209
        }
210
        $t->data['sppp'] = $privacypolicy;
211
212
        // Set focus element
213
        switch ($state['consent:focus']) {
214
            case 'yes':
215
                $t->data['autofocus'] = 'yesbutton';
216
                break;
217
            case 'no':
218
                $t->data['autofocus'] = 'nobutton';
219
                break;
220
            case null:
221
            default:
222
                break;
223
        }
224
225
        $t->data['usestorage'] = array_key_exists('consent:store', $state);
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->query->get('StateId');
239
        if ($stateId === null) {
240
            throw new Error\BadRequest('Missing required StateId query parameter.');
241
        }
242
243
        $state = $this->authState::loadState($stateId, 'consent:request');
244
        if (is_null($state)) {
245
            throw new Error\NoState();
246
        }
247
248
        $resumeFrom = Module::getModuleURL(
249
            'consent/getconsent',
250
            ['StateId' => $stateId]
251
        );
252
253
        $logoutLink = Module::getModuleURL(
254
            'consent/logout',
255
            ['StateId' => $stateId]
256
        );
257
258
        $aboutService = null;
259
        if (!isset($state['consent:showNoConsentAboutService']) || $state['consent:showNoConsentAboutService']) {
260
            if (isset($state['Destination']['url.about'])) {
261
                $aboutService = $state['Destination']['url.about'];
262
            }
263
        }
264
265
        $statsInfo = [];
266
        if (isset($state['Destination']['entityid'])) {
267
            $statsInfo['spEntityID'] = $state['Destination']['entityid'];
268
        }
269
        Stats::log('consent:reject', $statsInfo);
270
271
        if (array_key_exists('name', $state['Destination'])) {
272
            $dstName = $state['Destination']['name'];
273
        } elseif (array_key_exists('OrganizationDisplayName', $state['Destination'])) {
274
            $dstName = $state['Destination']['OrganizationDisplayName'];
275
        } else {
276
            $dstName = $state['Destination']['entityid'];
277
        }
278
279
        $t = new Template($this->config, 'consent:noconsent.twig');
280
        $translator = $t->getTranslator();
281
        $t->data['dstMetadata'] = $state['Destination'];
282
        $t->data['resumeFrom'] = $resumeFrom;
283
        $t->data['aboutService'] = $aboutService;
284
        $t->data['logoutLink'] = $logoutLink;
285
        $t->data['dstName'] = htmlspecialchars(
286
            is_array($dstName) ? $translator->getPreferredTranslation($dstName) : $dstName
0 ignored issues
show
Bug introduced by
The method getPreferredTranslation() 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

286
            is_array($dstName) ? $translator->/** @scrutinizer ignore-call */ getPreferredTranslation($dstName) : $dstName

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...
287
        );
288
        return $t;
289
    }
290
291
292
    /**
293
     * @param \Symfony\Component\HttpFoundation\Request $request The current request.
294
     *
295
     * @return \SimpleSAML\HTTP\RunnableResponse
296
     */
297
    public function logout(Request $request): RunnableResponse
298
    {
299
        $stateId = $request->query->get('StateId', null);
300
        if ($stateId === null) {
301
            throw new Error\BadRequest('Missing required StateId query parameter.');
302
        }
303
304
        $state = $this->authState::loadState($stateId, 'consent:request');
305
        if (is_null($state)) {
306
            throw new Error\NoState();
307
        }
308
        $state['Responder'] = ['\SimpleSAML\Module\consent\Logout', 'postLogout'];
309
310
        $idp = IdP::getByState($state);
311
        return new RunnableResponse([$idp, 'handleLogoutRequest'], [&$state, $stateId]);
312
    }
313
314
315
    /**
316
     * @return \SimpleSAML\XHTML\Template
317
     */
318
    public function logoutcompleted(): Template
319
    {
320
        return new Template($this->config, 'consent:logout_completed.twig');
321
    }
322
}
323