Passed
Push — develop ( c405ae...650212 )
by Nikolay
05:16
created

AdvicesProcessor::prepareSipFields()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 37
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 28
c 1
b 0
f 0
dl 0
loc 37
rs 9.472
cc 2
nc 2
nop 1
1
<?php
2
/*
3
 * MikoPBX - free phone system for small business
4
 * Copyright © 2017-2023 Alexey Portnov and Nikolay Beketov
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License along with this program.
17
 * If not, see <https://www.gnu.org/licenses/>.
18
 */
19
20
namespace MikoPBX\PBXCoreREST\Lib;
21
22
use MikoPBX\Common\Providers\ManagedCacheProvider;
23
use MikoPBX\Core\System\Processes;
24
use MikoPBX\Core\System\Storage;
25
use MikoPBX\Core\System\Util;
26
use MikoPBX\PBXCoreREST\Http\Response;
27
use MikoPBX\Common\Models\{AsteriskManagerUsers, Extensions, NetworkFilters, PbxSettings, Sip, Users};
28
use GuzzleHttp;
29
use Phalcon\Di;
30
use Phalcon\Di\Injectable;
31
use SimpleXMLElement;
32
use MikoPBX\Service\Main;
33
34
/**
35
 * Class AdvicesProcessor
36
 *
37
 * @package MikoPBX\PBXCoreREST\Lib
38
 *
39
 * @property \MikoPBX\Common\Providers\LicenseProvider license
40
 * @property \MikoPBX\Common\Providers\TranslationProvider translation
41
 * @property \Phalcon\Config config
42
 *
43
 */
44
class AdvicesProcessor extends Injectable
45
{
46
    public const ARR_ADVICE_TYPES = [
47
        ['type' => 'isConnected', 'cacheTime' => 60],
48
        ['type' => 'checkCorruptedFiles', 'cacheTime' => 600],
49
        ['type' => 'checkPasswords', 'cacheTime' => 86400],
50
        ['type' => 'checkFirewalls', 'cacheTime' => 86400],
51
        ['type' => 'checkStorage', 'cacheTime' => 600],
52
        ['type' => 'checkUpdates', 'cacheTime' => 3600],
53
        ['type' => 'checkRegistration', 'cacheTime' => 86400],
54
    ];
55
56
    /**
57
     * Processes the Advices request.
58
     *
59
     * @param array $request The request data.
60
     *
61
     * @return PBXApiResult An object containing the result of the API call.
62
     */
63
    public static function callBack(array $request): PBXApiResult
64
    {
65
        $res = new PBXApiResult();
66
        $res->processor = __METHOD__;
67
        $action = $request['action'];
68
        if ('getList' === $action) {
69
            $proc = new self();
70
            $res = $proc->getAdvicesAction();
71
        } else {
72
            $res->messages[] = "Unknown action - {$action} in advicesCallBack";
73
        }
74
        $res->function = $action;
75
        return $res;
76
    }
77
78
    /**
79
     * Generates a list of notifications about the system, firewall, passwords, and wrong settings.
80
     *
81
     * @return PBXApiResult An object containing the result of the API call.
82
     */
83
    private function getAdvicesAction(): PBXApiResult
84
    {
85
        $res = new PBXApiResult();
86
        $res->processor = __METHOD__;
87
        try {
88
            $arrMessages = [];
89
90
            $managedCache = $this->getDI()->getShared(ManagedCacheProvider::SERVICE_NAME);
91
            $language = PbxSettings::getValueByKey('WebAdminLanguage');
92
93
            foreach (self::ARR_ADVICE_TYPES as $adviceType) {
94
                $currentAdvice = $adviceType['type'];
95
                $cacheTime = $adviceType['cacheTime'];
96
                $cacheKey = self::getCacheKey($currentAdvice);
97
                if ($managedCache->has($cacheKey)) {
98
                    $oldResult = json_decode($managedCache->get($cacheKey), true, 512, JSON_THROW_ON_ERROR);
99
                    if ($language === $oldResult['LastLanguage']) {
100
                        $arrMessages[] = $oldResult['LastMessage'];
101
                        continue;
102
                    }
103
                }
104
                $newResult = $this->$currentAdvice();
105
                if (!empty($newResult)) {
106
                    $arrMessages[] = $newResult;
107
                }
108
                $managedCache->set(
109
                    $cacheKey,
110
                    json_encode([
111
                        'LastLanguage' => $language,
112
                        'LastMessage' => $newResult,
113
                    ], JSON_THROW_ON_ERROR),
114
                    $cacheTime
115
                );
116
            }
117
            $res->success = true;
118
            $result = [];
119
            foreach ($arrMessages as $message) {
120
                foreach ($message as $key => $value) {
121
                    if (is_array($value)) {
122
                        if (!isset($result[$key])) {
123
                            $result[$key] = [];
124
                        }
125
                        $result[$key] = array_merge($result[$key], $value);
126
                    } elseif (!empty($value)) {
127
                        $result[$key][] = $value;
128
                    }
129
                }
130
            }
131
            $res->data['advices'] = $result;
132
133
        } catch (\Throwable $e) {
134
            $res->success = false;
135
            $res->messages[] = $e->getMessage();
136
        }
137
        return $res;
138
    }
139
140
    /**
141
     * Prepares array of system passwords with representation to check password quality.
142
     *
143
     * @param array $fields
144
     * @return void
145
     */
146
    public function preparePasswordFields(array &$fields): array
147
    {
148
        $messages = [
149
            'warning' => [],
150
            'needUpdate' => []
151
        ];
152
153
        $arrOfDefaultValues = PbxSettings::getDefaultArrayValues();
154
        $fields = [
155
            'WebAdminPassword' => [
156
                'urlTemplate' => 'general-settings/modify/#/passwords',
157
                'type' => 'adv_WebPasswordFieldName',
158
                'value' => PbxSettings::getValueByKey('WebAdminPassword')
159
            ],
160
            'SSHPassword' => [
161
                'urlTemplate' => 'general-settings/modify/#/ssh',
162
                'type' => 'adv_SshPasswordFieldName',
163
                'value' => PbxSettings::getValueByKey('SSHPassword')
164
            ],
165
        ];
166
        if ($arrOfDefaultValues['WebAdminPassword'] === PbxSettings::getValueByKey('WebAdminPassword')) {
167
            $messages['warning'][] = $this->translation->_(
168
                'adv_YouUseDefaultWebPassword',
169
                ['url' => $this->url->get('general-settings/modify/#/passwords')]
170
            );
171
            unset($fields['WebAdminPassword']);
172
            $messages['needUpdate'][] = 'WebAdminPassword';
173
        }
174
        if ($arrOfDefaultValues['SSHPassword'] === PbxSettings::getValueByKey('SSHPassword')) {
175
            $messages['warning'][] = $this->translation->_(
176
                'adv_YouUseDefaultSSHPassword',
177
                ['url' => $this->url->get('general-settings/modify/#/ssh')]
178
            );
179
            unset($fields['SSHPassword']);
180
            $messages['needUpdate'][] = 'SSHPassword';
181
        } elseif (PbxSettings::getValueByKey('SSHPasswordHash') !== md5_file('/etc/passwd')) {
182
            $messages['warning'][] = $this->translation->_(
183
                'adv_SSHPPasswordCorrupt',
184
                ['url' => $this->url->get('general-settings/modify/#/ssh')]
185
            );
186
        }
187
        return $messages;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $messages returns the type array<string,array> which is incompatible with the documented return type void.
Loading history...
188
    }
189
190
    /**
191
     * Prepares array of ami passwords with representation to check password quality.
192
     * @param array $fields
193
     * @return void
194
     */
195
    public function prepareAmiFields(array &$fields): void
196
    {
197
        $amiUsersData = AsteriskManagerUsers::find([
198
                'columns' => 'id, username, secret']
199
        );
200
        foreach ($amiUsersData as $amiUser) {
201
            $fields[$amiUser->username] = [
202
                'urlTemplate' => 'asterisk-managers/modify/' . $amiUser->id,
203
                'type' => 'adv_AmiPasswordFieldName',
204
                'value' => $amiUser->secret
205
            ];
206
        }
207
    }
208
209
    /**
210
     * Prepares array of sip passwords with representation to check password quality.
211
     * @param array $fields
212
     * @return void
213
     */
214
    public function prepareSipFields(array &$fields): void
215
    {
216
        $parameters = [
217
            'models' => [
218
                'Extensions' => Extensions::class,
219
            ],
220
            'conditions' => 'Extensions.is_general_user_number = "1"',
221
            'columns' => [
222
                'id' => 'Extensions.id',
223
                'username' => 'Extensions.callerid',
224
                'number' => 'Extensions.number',
225
                'secret' => 'Sip.secret',
226
            ],
227
            'order' => 'number',
228
            'joins' => [
229
                'Sip' => [
230
                    0 => Sip::class,
231
                    1 => 'Sip.extension=Extensions.number',
232
                    2 => 'Sip',
233
                    3 => 'INNER',
234
                ],
235
                'Users' => [
236
                    0 => Users::class,
237
                    1 => 'Users.id = Extensions.userid',
238
                    2 => 'Users',
239
                    3 => 'INNER',
240
                ],
241
            ],
242
        ];
243
        $queryResult = $this->di->get('modelsManager')->createBuilder($parameters)->getQuery()->execute();
244
245
        foreach ($queryResult as $user) {
246
            $key = "{$user->username} <{$user->number}>";
247
            $fields[$key] = [
248
                'urlTemplate' => 'extensions/modify/' . $user->id,
249
                'type' => 'adv_UserPasswordFieldName',
250
                'value' => $user->secret
251
            ];
252
        }
253
    }
254
255
    /**
256
     * Check the quality of passwords.
257
     *
258
     * @return array An array containing warning and needUpdate messages.
259
     *
260
     * @noinspection PhpUnusedPrivateMethodInspection
261
     */
262
    private function checkPasswords(): array
263
    {
264
        $fields = [];
265
266
        // WebAdminPassword and SSHPassword
267
        $messages = $this->preparePasswordFields($fields);
268
269
        // SIP passwords
270
        $this->prepareSipFields($fields);
271
272
        // AMI Passwords
273
        $this->prepareAmiFields($fields);
274
275
        $cloudInstanceId = PbxSettings::getValueByKey('CloudInstanceId');
276
        foreach ($fields as $key => $value) {
277
            if ($cloudInstanceId !== $value['value'] && !Util::isSimplePassword($value['value'])) {
278
                continue;
279
            }
280
281
            if (in_array($key, ['WebAdminPassword', 'SSHPassword'], true)) {
282
                $messages['needUpdate'][] = $key;
283
            }
284
            $messages['warning'][] = $this->translation->_(
285
                'adv_isSimplePassword',
286
                [
287
                    'type' => $this->translation->_($value['type'], ['record' => $key]),
288
                    'url' => $this->url->get($value['urlTemplate']),
289
                ]
290
            );
291
        }
292
293
        return $messages;
294
    }
295
296
    /**
297
     * Check for corrupted files.
298
     *
299
     * @return array An array containing warning messages.
300
     */
301
    private function checkCorruptedFiles(): array
302
    {
303
        $messages = [];
304
        $files = Main::checkForCorruptedFiles();
305
        if (count($files) !== 0) {
306
            $messages['error'] =  $this->translation->_('adv_SystemBrokenComment', ['url' => '']);
307
        }
308
309
        return $messages;
310
    }
311
312
    /**
313
     * Check the firewall status.
314
     *
315
     * @return array An array containing warning messages.
316
     * @noinspection PhpUnusedPrivateMethodInspection
317
     */
318
    private function checkFirewalls(): array
319
    {
320
        $messages = [];
321
        if (PbxSettings::getValueByKey('PBXFirewallEnabled') === '0') {
322
            $messages['warning'] = $this->translation->_(
323
                'adv_FirewallDisabled',
324
                ['url' => $this->url->get('firewall/index/')]
325
            );
326
        }
327
        if (NetworkFilters::count() === 0) {
328
            $messages['warning'] = $this->translation->_(
329
                'adv_NetworksNotConfigured',
330
                ['url' => $this->url->get('firewall/index/')]
331
            );
332
        }
333
334
        return $messages;
335
    }
336
337
    /**
338
     * Check storage status.
339
     *
340
     * @return array An array containing warning or error messages.
341
     *
342
     * @noinspection PhpUnusedPrivateMethodInspection
343
     */
344
    private function checkStorage(): array
345
    {
346
        $messages = [];
347
        $st = new Storage();
348
        $storageDiskMounted = false;
349
        $disks = $st->getAllHdd();
350
        foreach ($disks as $disk) {
351
            if (array_key_exists('mounted', $disk)
352
                && strpos($disk['mounted'], '/storage/usbdisk') !== false) {
353
                $storageDiskMounted = true;
354
                if ($disk['free_space'] < 500) {
355
                    $messages['warning']
356
                        = $this->translation->_(
357
                        'adv_StorageDiskRunningOutOfFreeSpace',
358
                        ['free' => $disk['free_space']]
359
                    );
360
                }
361
            }
362
        }
363
        if ($storageDiskMounted === false) {
364
            $messages['error'] = $this->translation->_('adv_StorageDiskUnMounted');
365
        }
366
        return $messages;
367
    }
368
369
    /**
370
     * Check for a new version PBX
371
     *
372
     * @return array An array containing information messages about available updates.
373
     *
374
     * @noinspection PhpUnusedPrivateMethodInspection
375
     */
376
    private function checkUpdates(): array
377
    {
378
        $messages = [];
379
        $PBXVersion = PbxSettings::getValueByKey('PBXVersion');
380
381
        $client = new GuzzleHttp\Client();
382
        try {
383
            $res = $client->request(
384
                'POST',
385
                'https://releases.mikopbx.com/releases/v1/mikopbx/ifNewReleaseAvailable',
386
                [
387
                    'form_params' => [
388
                        'PBXVER' => $PBXVersion,
389
                    ],
390
                    'timeout' => 5,
391
                ]
392
            );
393
            $code = $res->getStatusCode();
394
        } catch (\Throwable $e) {
395
            $code = Response::INTERNAL_SERVER_ERROR;
396
            Util::sysLogMsg(static::class, $e->getMessage());
397
        }
398
399
        if ($code !== Response::OK) {
400
            return [];
401
        }
402
403
        $answer = json_decode($res->getBody(), false);
404
        if ($answer !== null && $answer->newVersionAvailable === true) {
405
            $messages['info'] = $this->translation->_(
406
                'adv_AvailableNewVersionPBX',
407
                [
408
                    'url' => $this->url->get('update/index/'),
409
                    'ver' => $answer->version,
410
                ]
411
            );
412
        }
413
414
        return $messages;
415
    }
416
417
    /**
418
     * Check mikopbx license status
419
     *
420
     * @noinspection PhpUnusedPrivateMethodInspection
421
     */
422
    private function checkRegistration(): array
423
    {
424
        $messages = [];
425
        $licKey = PbxSettings::getValueByKey('PBXLicense');
426
        if (!empty($licKey)) {
427
            $this->license->featureAvailable(33);
428
            $licenseInfo = $this->license->getLicenseInfo($licKey);
429
            if ($licenseInfo instanceof SimpleXMLElement) {
430
                file_put_contents('/tmp/licenseInfo', json_encode($licenseInfo->attributes()));
431
            }
432
        }
433
434
        return $messages;
435
    }
436
437
    /**
438
     * Checks whether internet connection is available or not
439
     *
440
     * @return array
441
     * @noinspection PhpUnusedPrivateMethodInspection
442
     */
443
    private function isConnected(): array
444
    {
445
        $messages = [];
446
        $pathTimeout = Util::which('timeout');
447
        $pathCurl = Util::which('curl');
448
        $retCode = Processes::mwExec("$pathTimeout 2 $pathCurl 'https://www.google.com/'");
449
        if ($retCode !== 0) {
450
            $messages['warning'] = $this->translation->_('adv_ProblemWithInternetConnection');
451
        }
452
        return $messages;
453
    }
454
455
    /**
456
     * Prepares redis cache key for advice type
457
     * @param string $currentAdviceType current advice type
458
     * @return string cache key
459
     */
460
    public static function getCacheKey(string  $currentAdviceType): string{
461
        return 'AdvicesProcessor:getAdvicesAction:' . $currentAdviceType;
462
    }
463
464
    /**
465
     * Cleanup cache for all advice types after change dependent models and PBX settings
466
     * on the WorkerModelsEvents worker.
467
     * @return void
468
     */
469
    public static function cleanupCache(): void{
470
471
        $dataIndependentAdviceTypes = [
472
            'isConnected',
473
            'checkCorruptedFiles',
474
            'checkStorage',
475
            'checkUpdates',
476
            'checkRegistration'
477
        ];
478
479
        $di = Di::getDefault();
480
        $managedCache = $di->getShared(ManagedCacheProvider::SERVICE_NAME);
481
        foreach (self::ARR_ADVICE_TYPES as $adviceType) {
482
            if (!in_array($adviceType['type'], $dataIndependentAdviceTypes)) {
483
                $cacheKey = self::getCacheKey($adviceType['type']);
484
                $managedCache->delete($cacheKey);
485
            }
486
        }
487
    }
488
}