Passed
Push — devel-3.0 ( 67d9a6...e0263f )
by Rubén
04:37
created

Init::initialize()   C

Complexity

Conditions 11
Paths 258

Size

Total Lines 114
Code Lines 55

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 55
nc 258
nop 1
dl 0
loc 114
rs 5.5884
c 0
b 0
f 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
 * sysPass
4
 *
5
 * @author    nuxsmin
6
 * @link      http://syspass.org
7
 * @copyright 2012-2017, Rubén Domínguez nuxsmin@$syspass.org
8
 *
9
 * This file is part of sysPass.
10
 *
11
 * sysPass is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU General Public License as published by
13
 * the Free Software Foundation, either version 3 of the License, or
14
 * (at your option) any later version.
15
 *
16
 * sysPass is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
 * GNU General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU General Public License
22
 *  along with sysPass.  If not, see <http://www.gnu.org/licenses/>.
23
 */
24
25
namespace SP\Modules\Web;
26
27
use Defuse\Crypto\Exception\CryptoException;
28
use Psr\Container\ContainerInterface;
29
use SP\Bootstrap;
30
use SP\Core\Context\ContextInterface;
31
use SP\Core\Context\SessionContext;
32
use SP\Core\Crypt\CryptSessionHandler;
33
use SP\Core\Crypt\Session as CryptSession;
34
use SP\Core\Crypt\UUIDCookie;
35
use SP\Core\Language;
36
use SP\Core\ModuleBase;
37
use SP\Core\UI\ThemeInterface;
38
use SP\DataModel\ItemPreset\SessionTimeout;
39
use SP\Http\Address;
40
use SP\Plugin\PluginManager;
41
use SP\Services\Crypt\SecureSessionService;
42
use SP\Services\ItemPreset\ItemPresetInterface;
43
use SP\Services\ItemPreset\ItemPresetService;
44
use SP\Services\Upgrade\UpgradeAppService;
45
use SP\Services\Upgrade\UpgradeDatabaseService;
46
use SP\Services\Upgrade\UpgradeUtil;
47
use SP\Services\UserProfile\UserProfileService;
48
use SP\Storage\Database\DatabaseUtil;
49
use SP\Util\HttpUtil;
50
51
/**
52
 * Class Init
53
 *
54
 * @property  itemPresetService
55
 * @package SP\Modules\Web
56
 */
57
final class Init extends ModuleBase
58
{
59
    /**
60
     * List of controllers that don't need to perform fully initialization
61
     * like: install/database checks, session/event handlers initialization
62
     */
63
    const PARTIAL_INIT = ['resource', 'install', 'bootstrap', 'status', 'upgrade', 'error'];
64
    /**
65
     * List of controllers that don't need to update the user's session activity
66
     */
67
    const NO_SESSION_ACTIVITY = ['items'];
68
69
    /**
70
     * @var SessionContext
71
     */
72
    private $context;
73
    /**
74
     * @var ThemeInterface
75
     */
76
    private $theme;
77
    /**
78
     * @var Language
79
     */
80
    private $language;
81
    /**
82
     * @var SecureSessionService
83
     */
84
    private $secureSessionService;
85
    /**
86
     * @var PluginManager
87
     */
88
    private $pluginManager;
89
    /**
90
     * @var ItemPresetService
91
     */
92
    private $itemPresetService;
93
    /**
94
     * @var bool
95
     */
96
    private $isIndex = false;
97
98
    /**
99
     * Init constructor.
100
     *
101
     * @param ContainerInterface $container
102
     */
103
    public function __construct(ContainerInterface $container)
104
    {
105
        parent::__construct($container);
106
107
        $this->context = $container->get(ContextInterface::class);
108
        $this->theme = $container->get(ThemeInterface::class);
109
        $this->language = $container->get(Language::class);
110
        $this->secureSessionService = $container->get(SecureSessionService::class);
111
        $this->pluginManager = $container->get(PluginManager::class);
112
        $this->itemPresetService = $container->get(ItemPresetService::class);
113
    }
114
115
    /**
116
     * Initialize Web App
117
     *
118
     * @param string $controller
119
     *
120
     * @throws \DI\DependencyException
121
     * @throws \DI\NotFoundException
122
     * @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException
123
     * @throws \SP\Core\Exceptions\ConstraintException
124
     * @throws \SP\Core\Exceptions\QueryException
125
     * @throws \SP\Core\Exceptions\SPException
126
     * @throws \SP\Repositories\NoSuchItemException
127
     * @throws \Exception
128
     */
129
    public function initialize($controller)
130
    {
131
        logger(__METHOD__);
132
133
        $this->isIndex = $controller === 'index';
134
135
        // Iniciar la sesión de PHP
136
        $this->initSession($this->configData->isEncryptSession());
137
138
        // Volver a cargar la configuración si se recarga la página
139
        if ($this->request->checkReload() === false) {
140
            // Cargar la configuración
141
            $this->config->loadConfig($this->context);
142
143
            // Cargar el lenguaje
144
            $this->language->setLanguage();
145
146
            // Initialize theme
147
            $this->theme->initTheme();
148
        } else {
149
            logger('Browser reload');
150
151
            $this->context->setAppStatus(SessionContext::APP_STATUS_RELOADED);
152
153
            // Cargar la configuración
154
            $this->config->loadConfig($this->context, true);
155
156
            // Restablecer el idioma
157
            $this->language->setLanguage(true);
158
159
            // Re-Initialize theme
160
            $this->theme->initTheme(true);
161
        }
162
163
        // Comprobar si es necesario cambiar a HTTPS
164
        HttpUtil::checkHttps($this->configData, $this->request);
165
166
        if (in_array($controller, self::PARTIAL_INIT, true) === false) {
167
            $databaseUtil = $this->container->get(DatabaseUtil::class);
168
169
            // Checks if sysPass is installed
170
            if (!$this->checkInstalled()) {
171
                logger('Not installed', 'ERROR');
172
173
                $this->router->response()
174
                    ->redirect('index.php?r=install/index')
175
                    ->send();
176
            }
177
178
            // Checks if the database is set up
179
            if (!$databaseUtil->checkDatabaseConnection()) {
180
                logger('Database connection error', 'ERROR');
181
182
                $this->router->response()
183
                    ->redirect('index.php?r=error/databaseConnection')
184
                    ->send();
185
            }
186
187
            // Checks if maintenance mode is turned on
188
            if ($this->checkMaintenanceMode($this->context)) {
189
                logger('Maintenance mode', 'INFO');
190
191
                $this->router->response()
192
                    ->redirect('index.php?r=error/maintenanceError')
193
                    ->send();
194
            }
195
196
            // Checks if upgrade is needed
197
            if ($this->checkUpgrade()) {
198
                logger('Upgrade needed', 'ERROR');
199
200
                $this->config->generateUpgradeKey();
201
202
                $this->router->response()
203
                    ->redirect('index.php?r=upgrade/index')
204
                    ->send();
205
            }
206
207
            // Checks if the database is set up
208
            if (!$databaseUtil->checkDatabaseTables($this->configData->getDbName())) {
209
                logger('Database checking error', 'ERROR');
210
211
                $this->router->response()
212
                    ->redirect('index.php?r=error/databaseError')
213
                    ->send();
214
            }
215
216
            // Initialize event handlers
217
            $this->initEventHandlers();
218
219
            if (!in_array($controller, self::NO_SESSION_ACTIVITY)) {
220
                // Initialize user session context
221
                $this->initUserSession();
222
            }
223
224
            // Load plugins
225
            $this->pluginManager->loadPlugins();
226
227
            if ($this->context->isLoggedIn()
228
                && $this->context->getAppStatus() === SessionContext::APP_STATUS_RELOADED
0 ignored issues
show
introduced by
The condition $this->context->getAppSt...xt::APP_STATUS_RELOADED is always false.
Loading history...
229
            ) {
230
                logger('Reload user profile');
231
                // Recargar los permisos del perfil de usuario
232
                $this->context->setUserProfile(
233
                    $this->container->get(UserProfileService::class)
234
                        ->getById($this->context->getUserData()
235
                            ->getUserProfileId())->getProfile());
236
            }
237
238
            return;
239
        }
240
241
        // Do not keep the PHP's session opened
242
        SessionContext::close();
243
    }
244
245
    /**
246
     * Iniciar la sesión PHP
247
     *
248
     * @param bool $encrypt Encriptar la sesión de PHP
249
     *
250
     * @throws \Exception
251
     */
252
    private function initSession($encrypt = false)
253
    {
254
        if ($encrypt === true
255
            && Bootstrap::$checkPhpVersion
256
            && ($key = $this->secureSessionService->getKey(UUIDCookie::factory($this->request))) !== false) {
257
            session_set_save_handler(new CryptSessionHandler($key), true);
258
        }
259
260
261
        try {
262
            $this->context->initialize();
263
        } catch (\Exception $e) {
264
            $this->router->response()->header('HTTP/1.1', '500 Internal Server Error');
265
266
            throw $e;
267
        }
268
    }
269
270
    /**
271
     * Comprueba que la aplicación esté instalada
272
     * Esta función comprueba si la aplicación está instalada. Si no lo está, redirige al instalador.
273
     */
274
    private function checkInstalled()
275
    {
276
        return $this->configData->isInstalled()
277
            && $this->router->request()->param('r') !== 'install/index';
278
    }
279
280
    /**
281
     * Comprobar si es necesario actualizar componentes
282
     */
283
    private function checkUpgrade()
284
    {
285
        return $this->configData->getUpgradeKey()
286
            || (UpgradeDatabaseService::needsUpgrade($this->configData->getDatabaseVersion()) ||
287
                UpgradeAppService::needsUpgrade(UpgradeUtil::fixVersionNumber($this->configData->getConfigVersion())));
288
    }
289
290
    /**
291
     * Inicializar la sesión de usuario
292
     *
293
     */
294
    private function initUserSession()
295
    {
296
        $lastActivity = $this->context->getLastActivity();
297
        $inMaintenance = $this->configData->isMaintenance();
298
299
        // Timeout de sesión
300
        if ($lastActivity > 0
301
            && !$inMaintenance
302
            && time() > ($lastActivity + $this->getSessionLifeTime())
303
        ) {
304
            if ($this->router->request()->cookies()->get(session_name()) !== null) {
305
                $this->router->response()->cookie(session_name(), '', time() - 42000);
306
            }
307
308
            SessionContext::restart();
309
        } else {
310
            $sidStartTime = $this->context->getSidStartTime();
311
312
            // Regenerar el Id de sesión periódicamente para evitar fijación
313
            if ($sidStartTime === 0) {
314
                // Intentar establecer el tiempo de vida de la sesión en PHP
315
                @ini_set('session.gc_maxlifetime', $this->getSessionLifeTime());
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for ini_set(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

315
                /** @scrutinizer ignore-unhandled */ @ini_set('session.gc_maxlifetime', $this->getSessionLifeTime());

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
316
317
                $this->context->setSidStartTime(time());
318
                $this->context->setStartActivity(time());
319
            } else if (!$inMaintenance
320
                && time() > ($sidStartTime + SessionContext::MAX_SID_TIME)
321
                && $this->context->isLoggedIn()
322
            ) {
323
                try {
324
                    CryptSession::reKey($this->context);
325
                } catch (CryptoException $e) {
326
                    logger($e->getMessage());
327
328
                    SessionContext::restart();
329
                    return;
330
                }
331
            }
332
333
            $this->context->setLastActivity(time());
334
        }
335
    }
336
337
    /**
338
     * Obtener el timeout de sesión desde la configuración.
339
     *
340
     * @return int con el tiempo en segundos
341
     */
342
    private function getSessionLifeTime()
343
    {
344
        $timeout = $this->context->getSessionTimeout();
345
346
        try {
347
            if ($this->isIndex || $timeout === null) {
348
                $userTimeout = $this->getSessionTimeoutForUser($timeout) ?: $this->configData->getSessionTimeout();
349
350
                logger('Session timeout: ' . $userTimeout);
351
352
                return $this->context->setSessionTimeout($userTimeout);
353
            }
354
        } catch (\Exception $e) {
355
            processException($e);
356
        }
357
358
        return $timeout;
359
    }
360
361
    /**
362
     * @param int $default
363
     *
364
     * @return int
365
     * @throws \SP\Core\Exceptions\ConstraintException
366
     * @throws \SP\Core\Exceptions\InvalidArgumentException
367
     * @throws \SP\Core\Exceptions\NoSuchPropertyException
368
     * @throws \SP\Core\Exceptions\QueryException
369
     */
370
    private function getSessionTimeoutForUser(int $default = null)
371
    {
372
        if ($this->context->isLoggedIn()) {
373
            $itemPreset = $this->itemPresetService->getForCurrentUser(ItemPresetInterface::ITEM_TYPE_SESSION_TIMEOUT);
374
375
            if ($itemPreset !== null) {
376
                $sessionTimeout = $itemPreset->hydrate(SessionTimeout::class);
377
378
                if (Address::check($this->request->getClientAddress(), $sessionTimeout->getAddress(), $sessionTimeout->getMask())) {
0 ignored issues
show
Bug introduced by
It seems like $this->request->getClientAddress() can also be of type array; however, parameter $address of SP\Http\Address::check() does only seem to accept string, 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

378
                if (Address::check(/** @scrutinizer ignore-type */ $this->request->getClientAddress(), $sessionTimeout->getAddress(), $sessionTimeout->getMask())) {
Loading history...
379
                    return $sessionTimeout->getTimeout();
380
                }
381
            }
382
        }
383
384
        return $default;
385
    }
386
}