Completed
Push — master ( 8fc13e...c19aa3 )
by Axel
06:21
created

ConfigController::allowedhtmlAction()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 40
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 21
nc 4
nop 5
dl 0
loc 40
rs 9.2728
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Zikula package.
7
 *
8
 * Copyright Zikula Foundation - https://ziku.la/
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Zikula\SecurityCenterModule\Controller;
15
16
use HTMLPurifier;
17
use HTMLPurifier_Config;
18
use HTMLPurifier_VarParser;
19
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
20
use Symfony\Component\Filesystem\Filesystem;
21
use Symfony\Component\HttpFoundation\RedirectResponse;
22
use Symfony\Component\HttpFoundation\Request;
23
use Symfony\Component\Routing\Annotation\Route;
24
use Symfony\Component\Routing\RouterInterface;
25
use Zikula\Bundle\CoreBundle\CacheClearer;
26
use Zikula\Bundle\CoreBundle\Controller\AbstractController;
27
use Zikula\Bundle\CoreBundle\DynamicConfigDumper;
28
use Zikula\Bundle\CoreBundle\HttpKernel\ZikulaKernel;
29
use Zikula\ExtensionsModule\Api\ApiInterface\VariableApiInterface;
30
use Zikula\ExtensionsModule\Api\VariableApi;
31
use Zikula\PermissionsModule\Annotation\PermissionCheck;
32
use Zikula\SecurityCenterModule\Constant;
33
use Zikula\SecurityCenterModule\Form\Type\ConfigType;
34
use Zikula\SecurityCenterModule\Helper\HtmlTagsHelper;
35
use Zikula\SecurityCenterModule\Helper\PurifierHelper;
36
use Zikula\ThemeModule\Engine\Annotation\Theme;
37
use Zikula\UsersModule\Helper\AccessHelper;
38
39
/**
40
 * Class ConfigController
41
 *
42
 * @Route("/config")
43
 * @PermissionCheck("admin")
44
 */
45
class ConfigController extends AbstractController
46
{
47
    /**
48
     * @Route("/config")
49
     * @Theme("admin")
50
     * @Template("@ZikulaSecurityCenterModule/Config/config.html.twig")
51
     *
52
     * @return array|RedirectResponse
53
     */
54
    public function configAction(
55
        Request $request,
56
        RouterInterface $router,
57
        VariableApiInterface $variableApi,
58
        DynamicConfigDumper $configDumper,
59
        CacheClearer $cacheClearer,
60
        AccessHelper $accessHelper
61
    ) {
62
        $modVars = $variableApi->getAll(VariableApi::CONFIG);
63
64
        $sessionName = $this->getParameter('zikula.session.name');
65
        $modVars['sessionname'] = $sessionName;
66
        $modVars['idshtmlfields'] = implode(PHP_EOL, $modVars['idshtmlfields']);
67
        $modVars['idsjsonfields'] = implode(PHP_EOL, $modVars['idsjsonfields']);
68
        $modVars['idsexceptions'] = implode(PHP_EOL, $modVars['idsexceptions']);
69
70
        $form = $this->createForm(ConfigType::class, $modVars);
71
        $form->handleRequest($request);
72
        if ($form->isSubmitted() && $form->isValid()) {
73
            if ($form->get('save')->isClicked()) {
0 ignored issues
show
Bug introduced by
The method isClicked() does not exist on Symfony\Component\Form\FormInterface. It seems like you code against a sub-type of Symfony\Component\Form\FormInterface such as Symfony\Component\Form\SubmitButton. ( Ignorable by Annotation )

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

73
            if ($form->get('save')->/** @scrutinizer ignore-call */ isClicked()) {
Loading history...
74
                $formData = $form->getData();
75
76
                $updateCheck = $formData['updatecheck'] ?? 1;
77
                $variableApi->set(VariableApi::CONFIG, 'updatecheck', $updateCheck);
78
                if (0 === $updateCheck) {
79
                    // if update checks are disabled, reset values to force new update check if re-enabled
80
                    $variableApi->set(VariableApi::CONFIG, 'updateversion', ZikulaKernel::VERSION);
81
                    $variableApi->set(VariableApi::CONFIG, 'updatelastchecked', 0);
82
                }
83
                $variableApi->set(VariableApi::CONFIG, 'updatefrequency', $formData['updatefrequency'] ?? 7);
84
85
                $variableApi->set(VariableApi::CONFIG, 'seclevel', $formData['seclevel'] ?? 'Medium');
86
87
                $secMedDays = $formData['secmeddays'] ?? 7;
88
                if ($secMedDays < 1 || $secMedDays > 365) {
89
                    $secMedDays = 7;
90
                }
91
                $variableApi->set(VariableApi::CONFIG, 'secmeddays', $secMedDays);
92
93
                $secInactiveMinutes = $formData['secinactivemins'] ?? 20;
94
                if ($secInactiveMinutes < 1 || $secInactiveMinutes > 1440) {
95
                    $secInactiveMinutes = 7;
96
                }
97
                $variableApi->set(VariableApi::CONFIG, 'secinactivemins', $secInactiveMinutes);
98
99
                $sessionStoreToFile = $formData['sessionstoretofile'] ?? 0;
100
                $sessionSavePath = $formData['sessionsavepath'] ?? '';
101
102
                // check session path config is writable (if method is being changed to session file storage)
103
                $causeLogout = false;
104
                $storeTypeCanBeWritten = true;
105
                if (1 === $sessionStoreToFile && !empty($sessionSavePath)) {
106
                    // fix path on windows systems
107
                    $sessionSavePath = str_replace('\\', '/', $sessionSavePath);
108
                    // sanitize the path
109
                    $sessionSavePath = trim(stripslashes($sessionSavePath));
110
111
                    // check if sessionsavepath is a dir and if it is writable
112
                    // if yes, we need to logout
113
                    $storeTypeCanBeWritten = is_dir($sessionSavePath) ? is_writable($sessionSavePath) : false;
114
                    $causeLogout = $storeTypeCanBeWritten;
115
116
                    if (false === $storeTypeCanBeWritten) {
117
                        // an error occured - we do not change the way of storing session data
118
                        $this->addFlash('error', 'Error! Session path not writeable!');
119
                        $sessionSavePath = '';
120
                    }
121
                }
122
                if (true === $storeTypeCanBeWritten) {
123
                    $variableApi->set(VariableApi::CONFIG, 'sessionstoretofile', $sessionStoreToFile);
124
                    $variableApi->set(VariableApi::CONFIG, 'sessionsavepath', $sessionSavePath);
125
                }
126
127
                if ((bool)$sessionStoreToFile !== (bool)$variableApi->getSystemVar('sessionstoretofile')) {
128
                    // logout if going from one storage to another one
129
                    $causeLogout = true;
130
                }
131
132
                $newSessionName = $formData['sessionname'] ?? $sessionName;
133
                if (mb_strlen($newSessionName) < 3) {
134
                    $newSessionName = $sessionName;
135
                }
136
137
                // cause logout if we changed session name
138
                if ($newSessionName !== $modVars['sessionname']) {
139
                    $causeLogout = true;
140
                }
141
142
                // set the session information in /config/dynamic/generated.yaml
143
                $configDumper->setParameter('zikula.session.name', $newSessionName);
144
                $sessionHandlerId = Constant::SESSION_STORAGE_FILE === $sessionStoreToFile
145
                    ? 'session.handler.native_file'
146
                    : 'zikula_core.bridge.http_foundation.doctrine_session_handler'
147
                ;
148
                $configDumper->setParameter('zikula.session.handler_id', $sessionHandlerId);
149
                $sessionStorageId = Constant::SESSION_STORAGE_FILE === $sessionStoreToFile
150
                    ? 'zikula_core.bridge.http_foundation.zikula_session_storage_file'
151
                    : 'zikula_core.bridge.http_foundation.zikula_session_storage_doctrine'
152
                ;
153
                $configDumper->setParameter('zikula.session.storage_id', $sessionStorageId); // Symfony default is 'session.storage.native'
154
                $zikulaSessionSavePath = empty($sessionSavePath) ? '%kernel.cache_dir%/sessions' : $sessionSavePath;
155
                $configDumper->setParameter('zikula.session.save_path', $zikulaSessionSavePath);
156
157
                $variableApi->set(VariableApi::CONFIG, 'sessionname', $newSessionName);
158
                $variableApi->set(VariableApi::CONFIG, 'sessionstoretofile', $sessionStoreToFile);
159
160
                $variableApi->set(VariableApi::CONFIG, 'outputfilter', $formData['outputfilter'] ?? 1);
161
162
                $useIds = $formData['useids'] ?? 0;
163
                $variableApi->set(VariableApi::CONFIG, 'useids', $useIds);
164
165
                // create tmp directory for PHPIDS
166
                if (1 === $useIds) {
167
                    $idsTmpDir = $this->getParameter('kernel.cache_dir') . '/idsTmp';
168
                    $fs = new Filesystem();
169
                    if (!$fs->exists($idsTmpDir)) {
170
                        $fs->mkdir($idsTmpDir);
171
                    }
172
                }
173
174
                $variableApi->set(VariableApi::CONFIG, 'idssoftblock', $formData['idssoftblock'] ?? 1);
175
                $variableApi->set(VariableApi::CONFIG, 'idsmail', $formData['idsmail'] ?? 0);
176
                $variableApi->set(VariableApi::CONFIG, 'idsfilter', $formData['idsfilter'] ?? 'xml');
177
178
                $idsRulePath = $formData['idsrulepath'] ?? 'system/SecurityCenterModule/Resources/config/phpids_zikula_default.xml';
179
                if (is_readable($idsRulePath)) {
180
                    $variableApi->set(VariableApi::CONFIG, 'idsrulepath', $idsRulePath);
181
                } else {
182
                    $this->addFlash('error', $this->trans('Error! PHPIDS rule file %filePath% does not exist or is not readable.', ['%filePath%' => $idsRulePath]));
183
                }
184
185
                $variableApi->set(VariableApi::CONFIG, 'idsimpactthresholdone', $formData['idsimpactthresholdone'] ?? 1);
186
                $variableApi->set(VariableApi::CONFIG, 'idsimpactthresholdtwo', $formData['idsimpactthresholdtwo'] ?? 10);
187
                $variableApi->set(VariableApi::CONFIG, 'idsimpactthresholdthree', $formData['idsimpactthresholdthree'] ?? 25);
188
                $variableApi->set(VariableApi::CONFIG, 'idsimpactthresholdfour', $formData['idsimpactthresholdfour'] ?? 75);
189
190
                $variableApi->set(VariableApi::CONFIG, 'idsimpactmode', $formData['idsimpactmode'] ?? 1);
191
192
                $idsHtmlFields = $formData['idshtmlfields'] ?? '';
193
                $idsHtmlFields = explode(PHP_EOL, $idsHtmlFields);
194
                $idsHtmlArray = [];
195
                foreach ($idsHtmlFields as $idsHtmlField) {
196
                    $idsHtmlField = trim($idsHtmlField);
197
                    if (!empty($idsHtmlField)) {
198
                        $idsHtmlArray[] = $idsHtmlField;
199
                    }
200
                }
201
                $variableApi->set(VariableApi::CONFIG, 'idshtmlfields', $idsHtmlArray);
202
203
                $idsJsonFields = $formData['idsjsonfields'] ?? '';
204
                $idsJsonFields = explode(PHP_EOL, $idsJsonFields);
205
                $idsJsonArray = [];
206
                foreach ($idsJsonFields as $idsJsonField) {
207
                    $idsJsonField = trim($idsJsonField);
208
                    if (!empty($idsJsonField)) {
209
                        $idsJsonArray[] = $idsJsonField;
210
                    }
211
                }
212
                $variableApi->set(VariableApi::CONFIG, 'idsjsonfields', $idsJsonArray);
213
214
                $idsExceptions = $formData['idsexceptions'] ?? '';
215
                $idsExceptions = explode(PHP_EOL, $idsExceptions);
216
                $idsExceptionsArray = [];
217
                foreach ($idsExceptions as $idsException) {
218
                    $idsException = trim($idsException);
219
                    if (!empty($idsException)) {
220
                        $idsExceptionsArray[] = $idsException;
221
                    }
222
                }
223
                $variableApi->set(VariableApi::CONFIG, 'idsexceptions', $idsExceptionsArray);
224
225
                // clear cache
226
                $cacheClearer->clear('symfony');
227
228
                // the module configuration has been updated successfuly
229
                $this->addFlash('status', 'Done! Configuration updated.');
230
231
                // we need to auto logout the user if essential session settings have been changed
232
                if (true === $causeLogout) {
233
                    $accessHelper->logout();
234
                    $this->addFlash('status', 'Session handling variables have changed. You must log in again.');
235
                    $returnPage = urlencode($router->generate('zikulasecuritycentermodule_config_config'));
236
237
                    return $this->redirectToRoute('zikulausersmodule_access_login', ['returnUrl' => $returnPage]);
238
                }
239
            } elseif ($form->get('cancel')->isClicked()) {
240
                $this->addFlash('status', 'Operation cancelled.');
241
            }
242
243
            return $this->redirectToRoute('zikulasecuritycentermodule_config_config');
244
        }
245
246
        return [
247
            'form' => $form->createView()
248
        ];
249
    }
250
251
    /**
252
     * @Route("/purifierconfig/{reset}")
253
     * @Theme("admin")
254
     * @Template("@ZikulaSecurityCenterModule/Config/purifierconfig.html.twig")
255
     *
256
     * HTMLPurifier configuration.
257
     *
258
     * @return array|RedirectResponse
259
     */
260
    public function purifierconfigAction(
261
        Request $request,
262
        PurifierHelper $purifierHelper,
263
        CacheClearer $cacheClearer,
264
        string $reset = null
265
    ) {
266
        if ('POST' === $request->getMethod()) {
267
            // Load HTMLPurifier Classes
268
            $purifier = $purifierHelper->getPurifier();
269
270
            // Update module variables.
271
            $config = $request->request->get('purifierConfig');
272
            $config = HTMLPurifier_Config::prepareArrayFromForm($config, false, true, true, $purifier->config->def);
273
274
            $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm(true, $purifier->config->def);
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type array expected by parameter $allowed of HTMLPurifier_Config::getAllowedDirectivesForForm(). ( Ignorable by Annotation )

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

274
            $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm(/** @scrutinizer ignore-type */ true, $purifier->config->def);
Loading history...
275
            foreach ($allowed as list($namespace, $directive)) {
276
                $directiveKey = $namespace . '.' . $directive;
277
                $def = $purifier->config->def->info[$directiveKey];
278
279
                if (isset($config[$namespace])
280
                        && array_key_exists($directive, $config[$namespace])
281
                        && null === $config[$namespace][$directive]) {
282
                    unset($config[$namespace][$directive]);
283
284
                    if (count($config[$namespace]) <= 0) {
285
                        unset($config[$namespace]);
286
                    }
287
                }
288
289
                if (isset($config[$namespace][$directive])) {
290
                    if (is_int($def)) {
291
                        $directiveType = abs($def);
292
                    } else {
293
                        $directiveType = $def->type ?? 0;
294
                    }
295
296
                    switch ($directiveType) {
297
                        case HTMLPurifier_VarParser::LOOKUP:
298
                            $value = explode(PHP_EOL, $config[$namespace][$directive]);
299
                            $config[$namespace][$directive] = [];
300
                            foreach ($value as $val) {
301
                                $val = trim($val);
302
                                if (!empty($val)) {
303
                                    $config[$namespace][$directive][$val] = true;
304
                                }
305
                            }
306
                            if (empty($config[$namespace][$directive])) {
307
                                unset($config[$namespace][$directive]);
308
                            }
309
                            break;
310
                        case HTMLPurifier_VarParser::ALIST:
311
                            $value = explode(PHP_EOL, $config[$namespace][$directive]);
312
                            $config[$namespace][$directive] = [];
313
                            foreach ($value as $val) {
314
                                $val = trim($val);
315
                                if (!empty($val)) {
316
                                    $config[$namespace][$directive][] = $val;
317
                                }
318
                            }
319
                            if (empty($config[$namespace][$directive])) {
320
                                unset($config[$namespace][$directive]);
321
                            }
322
                            break;
323
                        case HTMLPurifier_VarParser::HASH:
324
                            $value = explode(PHP_EOL, $config[$namespace][$directive]);
325
                            $config[$namespace][$directive] = [];
326
                            foreach ($value as $val) {
327
                                list($i, $v) = explode(':', $val);
328
                                $i = trim($i);
329
                                $v = trim($v);
330
                                if (!empty($i) && !empty($v)) {
331
                                    $config[$namespace][$directive][$i] = $v;
332
                                }
333
                            }
334
                            if (empty($config[$namespace][$directive])) {
335
                                unset($config[$namespace][$directive]);
336
                            }
337
                            break;
338
                    }
339
                }
340
341
                if (isset($config[$namespace])
342
                        && array_key_exists($directive, $config[$namespace])
343
                        && null === $config[$namespace][$directive]) {
344
                    unset($config[$namespace][$directive]);
345
346
                    if (count($config[$namespace]) <= 0) {
347
                        unset($config[$namespace]);
348
                    }
349
                }
350
            }
351
352
            $this->setVar('htmlpurifierConfig', serialize($config));
353
354
            // clear all cache and compile directories
355
            $cacheClearer->clear('symfony');
356
            $cacheClearer->clear('legacy');
357
358
            // the module configuration has been updated successfuly
359
            $this->addFlash('status', 'Done! Saved HTMLPurifier configuration.');
360
361
            return $this->redirectToRoute('zikulasecuritycentermodule_config_purifierconfig');
362
        }
363
364
        // load the configuration page
365
366
        if (isset($reset) && 'default' === $reset) {
367
            $purifierConfig = $purifierHelper->getPurifierConfig(['forcedefault' => true]);
368
            $this->addFlash('status', 'Default values for HTML Purifier were successfully loaded. Please store them using the "Save" button at the bottom of this page');
369
        } else {
370
            $purifierConfig = $purifierHelper->getPurifierConfig(['forcedefault' => false]);
371
        }
372
373
        $purifier = new HTMLPurifier($purifierConfig);
374
375
        $config = $purifier->config;
376
377
        if (is_array($config) && isset($config[0])) {
0 ignored issues
show
introduced by
The condition is_array($config) is always false.
Loading history...
378
            $config = $config[1];
379
        }
380
381
        $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm(true, $config->def);
382
383
        // list of excluded directives, format is $namespace_$directive
384
        $excluded = ['Cache_SerializerPath'];
385
386
        // Editing for only these types is supported
387
        $editableTypes = [
388
            HTMLPurifier_VarParser::C_STRING,
389
            HTMLPurifier_VarParser::ISTRING,
390
            HTMLPurifier_VarParser::TEXT,
391
            HTMLPurifier_VarParser::ITEXT,
392
            HTMLPurifier_VarParser::C_INT,
393
            HTMLPurifier_VarParser::C_FLOAT,
394
            HTMLPurifier_VarParser::C_BOOL,
395
            HTMLPurifier_VarParser::LOOKUP,
396
            HTMLPurifier_VarParser::ALIST,
397
            HTMLPurifier_VarParser::HASH
398
        ];
399
400
        $purifierAllowed = [];
401
        foreach ($allowed as list($namespace, $directive)) {
402
            if (in_array($namespace . '_' . $directive, $excluded, true)) {
403
                continue;
404
            }
405
406
            if ('Filter' === $namespace) {
407
                if (
408
                // Do not allow Filter.Custom for now. Causing errors.
409
                // TODO research why Filter.Custom is causing exceptions and correct.
410
                        ('Custom' === $directive)
411
                        // Do not allow Filter.ExtractStyleBlock* for now. Causing errors.
412
                        // TODO Filter.ExtractStyleBlock* requires CSSTidy
413
                        || (false !== mb_stripos($directive, 'ExtractStyleBlock'))
414
                ) {
415
                    continue;
416
                }
417
            }
418
419
            $directiveRec = [];
420
            $directiveRec['key'] = $namespace . '.' . $directive;
421
            $def = $config->def->info[$directiveRec['key']];
422
            $directiveRec['value'] = $config->get($directiveRec['key']);
423
            if (is_int($def)) {
424
                $directiveRec['allowNull'] = ($def < 0);
425
                $directiveRec['type'] = abs($def);
426
            } else {
427
                $directiveRec['allowNull'] = (isset($def->allow_null) && $def->allow_null);
428
                $directiveRec['type'] = ($def->type ?? 0);
429
                if (isset($def->allowed)) {
430
                    $directiveRec['allowedValues'] = [];
431
                    foreach ($def->allowed as $val => $b) {
432
                        $directiveRec['allowedValues'][] = $val;
433
                    }
434
                }
435
            }
436
            if (is_array($directiveRec['value'])) {
437
                switch ($directiveRec['type']) {
438
                    case HTMLPurifier_VarParser::LOOKUP:
439
                        $value = [];
440
                        foreach ($directiveRec['value'] as $val => $b) {
441
                            $value[] = $val;
442
                        }
443
                        $directiveRec['value'] = implode(PHP_EOL, $value);
444
                        break;
445
                    case HTMLPurifier_VarParser::ALIST:
446
                        $directiveRec['value'] = implode(PHP_EOL, $directiveRec['value']);
447
                        break;
448
                    case HTMLPurifier_VarParser::HASH:
449
                        $directiveRec['value'] = json_encode($directiveRec['value']);
450
                        break;
451
                    default:
452
                        $directiveRec['value'] = '';
453
                }
454
            }
455
456
            $directiveRec['supported'] = in_array($directiveRec['type'], $editableTypes, true);
457
458
            $purifierAllowed[$namespace][$directive] = $directiveRec;
459
        }
460
461
        return [
462
            'purifier' => $purifier,
463
            'purifierTypes' => HTMLPurifier_VarParser::$types,
464
            'purifierAllowed' => $purifierAllowed
465
        ];
466
    }
467
468
    /**
469
     * @Route("/allowedhtml")
470
     * @Theme("admin")
471
     * @Template("@ZikulaSecurityCenterModule/Config/allowedhtml.html.twig")
472
     *
473
     * Display the allowed html form.
474
     *
475
     * @return array|RedirectResponse
476
     */
477
    public function allowedhtmlAction(
478
        Request $request,
479
        RouterInterface $router,
480
        VariableApiInterface $variableApi,
481
        CacheClearer $cacheClearer,
482
        HtmlTagsHelper $htmlTagsHelper
483
    ) {
484
        $htmlTags = $htmlTagsHelper->getTagsWithLinks();
485
486
        if ('POST' === $request->getMethod()) {
487
            $htmlEntities = $request->request->getInt('htmlentities', 0);
488
            $variableApi->set(VariableApi::CONFIG, 'htmlentities', $htmlEntities);
489
490
            // update the allowed html settings
491
            $allowedHtml = [];
492
            foreach ($htmlTags as $htmlTag => $usageTag) {
493
                $tagVal = $request->request->getInt('htmlallow' . $htmlTag . 'tag', 0);
494
                if (1 !== $tagVal && 2 !== $tagVal) {
495
                    $tagVal = 0;
496
                }
497
                $allowedHtml[$htmlTag] = $tagVal;
498
            }
499
500
            $variableApi->set(VariableApi::CONFIG, 'AllowableHTML', $allowedHtml);
501
502
            // clear all cache and compile directories
503
            $cacheClearer->clear('symfony');
504
            $cacheClearer->clear('legacy');
505
506
            $this->addFlash('status', 'Done! Configuration updated.');
507
508
            return $this->redirectToRoute('zikulasecuritycentermodule_config_allowedhtml');
509
        }
510
511
        return [
512
            'htmlEntities' => $variableApi->getSystemVar('htmlentities'),
513
            'htmlPurifier' => 1 === $variableApi->getSystemVar('outputfilter'),
514
            'configUrl' => $router->generate('zikulasecuritycentermodule_config_config'),
515
            'htmlTags' => $htmlTags,
516
            'currentHtmlTags' => $variableApi->getSystemVar('AllowableHTML')
517
        ];
518
    }
519
}
520