Passed
Pull Request — master (#16)
by Nikolay
13:10 queued 02:12
created

FirewallController::indexAction()   F

Complexity

Conditions 17
Paths 1672

Size

Total Lines 99
Code Lines 68

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 68
dl 0
loc 99
rs 1.0499
c 0
b 0
f 0
cc 17
nc 1672
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Copyright (C) MIKO LLC - All Rights Reserved
4
 * Unauthorized copying of this file, via any medium is strictly prohibited
5
 * Proprietary and confidential
6
 * Written by Nikolay Beketov, 5 2018
7
 *
8
 */
9
10
namespace MikoPBX\AdminCabinet\Controllers;
11
12
use MikoPBX\AdminCabinet\Forms\FirewallEditForm;
13
use MikoPBX\AdminCabinet\Library\Cidr;
14
use MikoPBX\Common\Models\{FirewallRules, LanInterfaces, NetworkFilters, PbxSettings};
15
16
class FirewallController extends BaseController
17
{
18
19
20
    /**
21
     * Построение таблицы доступа к ресурсам системы
22
     */
23
    public function indexAction(): void
24
    {
25
        $calculator        = new Cidr();
26
        $localAddresses    = [];
27
        $localAddresses[]  = '0.0.0.0/0';
28
        $conditions        = 'disabled=0 AND internet=0'; // Нам нужны только локальные включенные сети
29
        $networkInterfaces = LanInterfaces::find($conditions);
30
        foreach ($networkInterfaces as $interface) {
31
            if (empty($interface->ipaddr)) {
32
                continue;
33
            }
34
35
            if (strpos($interface->subnet, '.') === false) {
36
                $localAddresses[] = $calculator->cidr2network(
37
                        $interface->ipaddr,
38
                        $interface->subnet
39
                    ) . '/' . $interface->subnet;
40
            } else {
41
                $cidr             = $calculator->netmask2cidr($interface->subnet);
42
                $localAddresses[] = $calculator->cidr2network($interface->ipaddr, $cidr) . '/' . $cidr;
43
            }
44
        }
45
46
        $defaultRules             = FirewallRules::getDefaultRules();
47
        $networksTable            = [];
48
        $networkFilters           = NetworkFilters::find();
49
        $networkFiltersStoredInDB = ($networkFilters->count() > 0);
50
        foreach ($networkFilters as $filter) {
51
            $networksTable[$filter->id]['id']          = $filter->id;
52
            $networksTable[$filter->id]['description'] = $filter->description;
53
54
            $permitParts = explode('/', $filter->permit);
55
56
            if (strpos($permitParts[1], '.') === false) {
57
                $networksTable[$filter->id]['network'] = $calculator->cidr2network(
58
                        $permitParts[0],
59
                        $permitParts[1]
60
                    ) . '/' . $permitParts[1];
61
            } else {
62
                $cidr                                  = $calculator->netmask2cidr($permitParts[1]);
63
                $networksTable[$filter->id]['network'] = $calculator->cidr2network(
64
                        $permitParts[0],
65
                        $cidr
66
                    ) . '/' . $cidr;
67
            }
68
            $networksTable[$filter->id]['permanent'] = false;
69
70
71
            // Заполним значениями по умолчанию
72
            foreach ($defaultRules as $key => $value) {
73
                $networksTable[$filter->id]['category'][$key] = [
74
                    'name'   => empty($value['shortName']) ? $key : $value['shortName'],
75
                    'action' => $value['action'],
76
                ];
77
            }
78
79
            // Заполним сохраненными ранее значениями
80
            $firewallRules = $filter->FirewallRules;
81
            foreach ($firewallRules as $rule) {
82
                $networksTable[$filter->id]['category'][$rule->category]['action'] = $rule->action;
83
                if ( ! array_key_exists('name', $networksTable[$filter->id]['category'][$rule->category])) {
84
                    $networksTable[$filter->id]['category'][$rule->category]['name'] = $rule->category;
85
                }
86
            }
87
        }
88
89
        // Добавиим фильтры по умолчанию, если они еще не добавлены.
90
        foreach ($localAddresses as $localAddress) {
91
            $existsPersistentRecord = false;
92
            foreach ($networksTable as $key => $value) {
93
                if ($value['network'] === $localAddress) {
94
                    $networksTable[$key]['permanent'] = true;
95
                    $existsPersistentRecord           = true;
96
                    break;
97
                }
98
            }
99
            if ( ! $existsPersistentRecord) {
100
                foreach ($defaultRules as $key => $value) {
101
                    $networksTableNewRecord['category'][$key] = [
102
                        'name'   => $key,
103
                        'action' => $networkFiltersStoredInDB ? 'block' : $value['action'],
104
                    ];
105
                }
106
                $networksTableNewRecord['id']        = '';
107
                $networksTableNewRecord['permanent'] = true;
108
                $networksTableNewRecord['network']   = $localAddress;
109
                if ($localAddress === '0.0.0.0/0') {
110
                    $networksTableNewRecord['description'] = $this->translation->_('fw_AllNetworksRule');
111
                } else {
112
                    $networksTableNewRecord['description'] = $this->translation->_('fw_LocalNetworksRule');
113
                }
114
                $networksTable[] = $networksTableNewRecord;
115
            }
116
        }
117
118
        usort($networksTable, [__CLASS__, 'sortArrayByNetwork']);
119
120
        $this->view->rulesTable         = $networksTable;
121
        $this->view->PBXFirewallEnabled = PbxSettings::getValueByKey('PBXFirewallEnabled');
122
    }
123
124
125
    /**
126
     * Форма редактирования карточки сетевого фильтра
127
     *
128
     * @param string $networkId
129
     */
130
    public function modifyAction(string $networkId = ''): void
131
    {
132
        $networkFilter = NetworkFilters::findFirstById($networkId);
133
        $firewallRules = FirewallRules::getDefaultRules();
134
        $data          = $this->request->getPost();
135
        if ($networkFilter === null) {
136
            $networkFilter         = new NetworkFilters();
137
            $networkFilter->permit = empty($data['permit']) ? '0.0.0.0/0' : $data['permit'];
138
        } else {
139
            // Заполним сохраненными ранее значениями
140
            foreach ($networkFilter->FirewallRules as $rule) {
141
                $firewallRules[$rule->category]['action'] = $rule->action;
142
            }
143
        }
144
        $permitParts = explode('/', $networkFilter->permit);
145
146
        $this->view->form          = new FirewallEditForm(
147
            $networkFilter,
148
            ['network' => $permitParts[0], 'subnet' => $permitParts[1]]
149
        );
150
        $this->view->firewallRules = $firewallRules;
151
        $this->view->represent     = $networkFilter->getRepresent();
152
    }
153
154
155
    /**
156
     * Проверка на доступность номера
157
     */
158
    public function saveAction(): void
159
    {
160
        if ( ! $this->request->isPost()) {
161
            return;
162
        }
163
164
        $this->db->begin();
165
166
        $data         = $this->request->getPost();
167
        $networkId    = $this->request->getPost('id');
168
        $filterRecord = NetworkFilters::findFirstById($networkId);
169
        if ($filterRecord === null) {
170
            $filterRecord = new NetworkFilters();
171
        }
172
173
        // Update network filters Network Filter
174
        if ( ! $this->updateNetworkFilters($filterRecord, $data)) {
175
            $this->view->success = false;
176
            $this->db->rollback();
177
178
            return;
179
        }
180
181
        // If it was new entity we will reload page with new ID
182
        if (empty($data['id'])) {
183
            $this->view->reload = "firewall/modify/{$filterRecord->id}";
184
        }
185
186
        // Update firewall rules Firewall
187
        $data['id'] = $filterRecord->id;
188
        if ( ! $this->updateFirewallRules($data)) {
189
            $this->view->success = false;
190
            $this->db->rollback();
191
192
            return;
193
        }
194
195
        $this->flash->success($this->translation->_('ms_SuccessfulSaved'));
196
        $this->view->success = true;
197
        $this->db->commit();
198
    }
199
200
    /**
201
     * Заполним параметры записи Network Filter
202
     *
203
     * @param \MikoPBX\Common\Models\NetworkFilters $filterRecord
204
     * @param array                                 $data массив полей из POST запроса
205
     *
206
     * @return bool update result
207
     */
208
    private function updateNetworkFilters(NetworkFilters $filterRecord, array $data): bool
209
    {
210
        $calculator = new Cidr();
211
        // Заполним параметры записи Network Filter
212
        foreach ($filterRecord as $name => $value) {
213
            switch ($name) {
214
                case 'permit':
215
                    $filterRecord->$name = $calculator->cidr2network(
216
                            $data['network'],
217
                            $data['subnet']
218
                        ) . '/' . $data['subnet'];
219
                    break;
220
                case 'deny':
221
                    $filterRecord->$name = '0.0.0.0/0';
222
                    break;
223
                case 'local_network':
224
                case 'newer_block_ip':
225
                    if (array_key_exists($name, $data) && $data[$name] === 'on') {
226
                        $filterRecord->$name = 1;
227
                    } else {
228
                        $filterRecord->$name = 0;
229
                    }
230
                    break;
231
                default:
232
                    if (array_key_exists($name, $data)) {
233
                        $filterRecord->$name = $data[$name];
234
                    }
235
            }
236
        }
237
238
        if ($filterRecord->save() === false) {
239
            $errors = $filterRecord->getMessages();
240
            $this->flash->error(implode('<br>', $errors));
241
242
            return false;
243
        }
244
245
        return true;
246
    }
247
248
    /**
249
     * Updates firewall rules
250
     *
251
     * @param array $data POST parameters array
252
     *
253
     * @return bool update result
254
     */
255
    private function updateFirewallRules(array $data): bool
256
    {
257
        // Get default rules
258
        $defaultRules      = FirewallRules::getDefaultRules();
259
        $countDefaultRules = 0;
260
        foreach ($defaultRules as $key => $value) {
261
            foreach ($value['rules'] as $rule) {
262
                $countDefaultRules++;
263
            }
264
        }
265
266
        // Delete outdated records
267
        $parameters        = [
268
            'conditions' => 'networkfilterid=:networkfilterid:',
269
            'bind'       => [
270
                'networkfilterid' => $data['id'],
271
            ],
272
        ];
273
        $firewallRules     = FirewallRules::find($parameters);
274
        $currentRulesCount = $firewallRules->count();
275
        while ($countDefaultRules < $currentRulesCount) {
276
            $firewallRules->next();
277
            if ($firewallRules->current()->delete() === false) {
278
                $errors = $firewallRules->getMessages();
279
                $this->flash->error(implode('<br>', $errors));
280
                $this->view->success = false;
281
                return false;
282
            } else {
283
                $currentRulesCount--;
284
            }
285
        }
286
        $firewallRules = FirewallRules::find($parameters);
287
        $rowId         = 0;
288
        foreach ($defaultRules as $key => $value) {
289
            foreach ($value['rules'] as $rule) {
290
                if ($firewallRules->offsetExists($rowId)) {
291
                    $newRule = $firewallRules->offsetGet($rowId);
292
                } else {
293
                    $newRule = new FirewallRules();
294
                }
295
                $newRule->networkfilterid = $data['id'];
296
                $newRule->protocol        = $rule['protocol'];
297
                $newRule->portfrom        = $rule['portfrom'];
298
                $newRule->portto          = $rule['portto'];
299
                $newRule->category        = $key;
300
301
                if (array_key_exists('rule_' . $key, $data) && $data['rule_' . $key]) {
302
                    $newRule->action = $data['rule_' . $key] === 'on' ? 'allow' : 'block';
303
                } else {
304
                    $newRule->action = 'block';
305
                }
306
                $newRule->description = "{$newRule->action} connection from network: {$data['network']} / {$data['subnet']}";
307
308
                if ($newRule->save() === false) {
309
                    $errors = $newRule->getMessages();
310
                    $this->flash->error(implode('<br>', $errors));
311
                    $this->view->success = false;
312
313
                    return false;
314
                }
315
                $rowId++;
316
            }
317
        }
318
319
        return true;
320
    }
321
322
    /**
323
     * Удаление правил настройки firewall
324
     *
325
     * @param string $networkId
326
     */
327
    public function deleteAction(string $networkId = '')
328
    {
329
        $this->db->begin();
330
        $filterRecord = NetworkFilters::findFirstById($networkId);
331
332
        $errors = null;
333
        if ($filterRecord !== null && ! $filterRecord->delete()) {
334
            $errors = $filterRecord->getMessages();
335
        }
336
337
        if ($errors) {
338
            $this->flash->warning(implode('<br>', $errors));
339
            $this->db->rollback();
340
        } else {
341
            $this->db->commit();
342
        }
343
344
        $this->forward('firewall/index');
345
    }
346
347
    /**
348
     * Включение firewall
349
     */
350
    public function enableAction(): void
351
    {
352
        $fail2BanEnabled = PbxSettings::findFirstByKey('PBXFail2BanEnabled');
353
        if ($fail2BanEnabled === null) {
354
            $fail2BanEnabled      = new PbxSettings();
355
            $fail2BanEnabled->key = 'PBXFail2BanEnabled';
356
        }
357
        $fail2BanEnabled->value = '1';
358
        if ($fail2BanEnabled->save() === false) {
359
            $errors = $fail2BanEnabled->getMessages();
360
            $this->flash->warning(implode('<br>', $errors));
361
            $this->view->success = false;
362
363
            return;
364
        }
365
366
        $firewallEnabled = PbxSettings::findFirstByKey('PBXFirewallEnabled');
367
        if ($firewallEnabled === null) {
368
            $firewallEnabled      = new PbxSettings();
369
            $firewallEnabled->key = 'PBXFail2BanEnabled';
370
        }
371
        $firewallEnabled->value = '1';
372
        if ($firewallEnabled->save() === false) {
373
            $errors = $firewallEnabled->getMessages();
374
            $this->flash->warning(implode('<br>', $errors));
375
            $this->view->success = false;
376
377
            return;
378
        }
379
        $this->view->success = true;
380
    }
381
382
    /**
383
     * Выключение firewall
384
     */
385
    public function disableAction(): void
386
    {
387
        $fail2BanEnabled = PbxSettings::findFirstByKey('PBXFail2BanEnabled');
388
        if ($fail2BanEnabled === null) {
389
            $fail2BanEnabled      = new PbxSettings();
390
            $fail2BanEnabled->key = 'PBXFail2BanEnabled';
391
        }
392
        $fail2BanEnabled->value = '0';
393
        if ($fail2BanEnabled->save() === false) {
394
            $errors = $fail2BanEnabled->getMessages();
395
            $this->flash->warning(implode('<br>', $errors));
396
            $this->view->success = false;
397
398
            return;
399
        }
400
401
        $firewallEnabled = PbxSettings::findFirstByKey('PBXFirewallEnabled');
402
        if ($firewallEnabled === null) {
403
            $firewallEnabled      = new PbxSettings();
404
            $firewallEnabled->key = 'PBXFail2BanEnabled';
405
        }
406
        $firewallEnabled->value = '0';
407
        if ($firewallEnabled->save() === false) {
408
            $errors = $firewallEnabled->getMessages();
409
            $this->flash->warning(implode('<br>', $errors));
410
            $this->view->success = false;
411
412
            return;
413
        }
414
        $this->view->success = true;
415
    }
416
417
    /**
418
     * Метод сортировки, локальная сеть и 0 сеть всегда сверху списка должны быть
419
     *
420
     * @param $a
421
     * @param $b
422
     *
423
     * @return bool
424
     */
425
    private function sortArrayByNetwork($a, $b): bool
426
    {
427
        if ($b['permanent'] && $a['network'] !== '0.0.0.0/0') {
428
            return true;
429
        }
430
        if ($b['network'] === '0.0.0.0/0') {
431
            return true;
432
        }
433
434
        return false;
435
    }
436
}