Issues (493)

app/modules/web/Init.php (1 issue)

1
<?php
2
/**
3
 * sysPass
4
 *
5
 * @author    nuxsmin
6
 * @link      https://syspass.org
7
 * @copyright 2012-2019, 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 Defuse\Crypto\Exception\EnvironmentIsBrokenException;
29
use DI\DependencyException;
30
use DI\NotFoundException;
31
use Exception;
32
use Psr\Container\ContainerInterface;
33
use SP\Bootstrap;
34
use SP\Core\Context\ContextInterface;
35
use SP\Core\Context\SessionContext;
36
use SP\Core\Crypt\CryptSessionHandler;
37
use SP\Core\Crypt\Session as CryptSession;
38
use SP\Core\Crypt\UUIDCookie;
39
use SP\Core\Exceptions\ConstraintException;
40
use SP\Core\Exceptions\InvalidArgumentException;
41
use SP\Core\Exceptions\NoSuchPropertyException;
42
use SP\Core\Exceptions\QueryException;
43
use SP\Core\Exceptions\SPException;
44
use SP\Core\Language;
45
use SP\Core\ModuleBase;
46
use SP\Core\UI\ThemeInterface;
47
use SP\DataModel\ItemPreset\SessionTimeout;
48
use SP\Http\Address;
49
use SP\Plugin\PluginManager;
50
use SP\Repositories\NoSuchItemException;
51
use SP\Services\Crypt\SecureSessionService;
52
use SP\Services\ItemPreset\ItemPresetInterface;
53
use SP\Services\ItemPreset\ItemPresetService;
54
use SP\Services\Upgrade\UpgradeAppService;
55
use SP\Services\Upgrade\UpgradeDatabaseService;
56
use SP\Services\Upgrade\UpgradeUtil;
57
use SP\Services\UserProfile\UserProfileService;
58
use SP\Storage\Database\DatabaseUtil;
59
use SP\Storage\File\FileException;
60
use SP\Util\HttpUtil;
61
62
/**
63
 * Class Init
64
 *
65
 * @property  itemPresetService
66
 * @package SP\Modules\Web
67
 */
68
final class Init extends ModuleBase
69
{
70
    /**
71
     * List of controllers that don't need to perform fully initialization
72
     * like: install/database checks, session/event handlers initialization
73
     */
74
    const PARTIAL_INIT = [
75
        'resource',
76
        'install',
77
        'bootstrap',
78
        'status',
79
        'upgrade',
80
        'error',
81
        'task'
82
    ];
83
    /**
84
     * List of controllers that don't need to update the user's session activity
85
     */
86
    const NO_SESSION_ACTIVITY = ['items', 'login'];
87
88
    /**
89
     * @var SessionContext
90
     */
91
    private $context;
92
    /**
93
     * @var ThemeInterface
94
     */
95
    private $theme;
96
    /**
97
     * @var Language
98
     */
99
    private $language;
100
    /**
101
     * @var SecureSessionService
102
     */
103
    private $secureSessionService;
104
    /**
105
     * @var PluginManager
106
     */
107
    private $pluginManager;
108
    /**
109
     * @var ItemPresetService
110
     */
111
    private $itemPresetService;
112
    /**
113
     * @var bool
114
     */
115
    private $isIndex = false;
116
117
    /**
118
     * Init constructor.
119
     *
120
     * @param ContainerInterface $container
121
     */
122
    public function __construct(ContainerInterface $container)
123
    {
124
        parent::__construct($container);
125
126
        $this->context = $container->get(ContextInterface::class);
127
        $this->theme = $container->get(ThemeInterface::class);
128
        $this->language = $container->get(Language::class);
129
        $this->secureSessionService = $container->get(SecureSessionService::class);
130
        $this->pluginManager = $container->get(PluginManager::class);
131
        $this->itemPresetService = $container->get(ItemPresetService::class);
132
    }
133
134
    /**
135
     * Initialize Web App
136
     *
137
     * @param string $controller
138
     *
139
     * @throws DependencyException
140
     * @throws NotFoundException
141
     * @throws EnvironmentIsBrokenException
142
     * @throws ConstraintException
143
     * @throws QueryException
144
     * @throws SPException
145
     * @throws NoSuchItemException
146
     * @throws Exception
147
     */
148
    public function initialize($controller)
149
    {
150
        logger(__METHOD__);
151
152
        $this->isIndex = $controller === 'index';
153
154
        // Iniciar la sesión de PHP
155
        $this->initSession($this->configData->isEncryptSession());
156
157
        $isReload = $this->request->checkReload();
158
159
        // Volver a cargar la configuración si se recarga la página
160
        if ($isReload) {
161
            logger('Browser reload');
162
163
            $this->context->setAppStatus(SessionContext::APP_STATUS_RELOADED);
164
165
            // Cargar la configuración
166
            $this->config->loadConfig(true);
167
        }
168
169
        // Setup language
170
        $this->language->setLanguage($isReload);
171
172
        // Setup theme
173
        $this->theme->initTheme($isReload);
174
175
        // Comprobar si es necesario cambiar a HTTPS
176
        HttpUtil::checkHttps($this->configData, $this->request);
177
178
        if (in_array($controller, self::PARTIAL_INIT, true) === false) {
179
            $databaseUtil = $this->container->get(DatabaseUtil::class);
180
181
            // Checks if sysPass is installed
182
            if (!$this->checkInstalled()) {
183
                logger('Not installed', 'ERROR');
184
185
                $this->router->response()
186
                    ->redirect('index.php?r=install/index')
187
                    ->send();
188
189
                return;
190
            }
191
192
            // Checks if the database is set up
193
            if (!$databaseUtil->checkDatabaseConnection()) {
194
                logger('Database connection error', 'ERROR');
195
196
                $this->router->response()
197
                    ->redirect('index.php?r=error/databaseConnection')
198
                    ->send();
199
200
                return;
201
            }
202
203
            // Checks if maintenance mode is turned on
204
            if ($this->checkMaintenanceMode($this->context)) {
205
                logger('Maintenance mode', 'INFO');
206
207
                $this->router->response()
208
                    ->redirect('index.php?r=error/maintenanceError')
209
                    ->send();
210
211
                return;
212
            }
213
214
            // Checks if upgrade is needed
215
            if ($this->checkUpgrade()) {
216
                logger('Upgrade needed', 'ERROR');
217
218
                $this->config->generateUpgradeKey();
219
220
                $this->router->response()
221
                    ->redirect('index.php?r=upgrade/index')
222
                    ->send();
223
224
                return;
225
            }
226
227
            // Checks if the database is set up
228
            if (!$databaseUtil->checkDatabaseTables($this->configData->getDbName())) {
229
                logger('Database checking error', 'ERROR');
230
231
                $this->router->response()
232
                    ->redirect('index.php?r=error/databaseError')
233
                    ->send();
234
235
                return;
236
            }
237
238
            // Initialize event handlers
239
            $this->initEventHandlers();
240
241
            if (!in_array($controller, self::NO_SESSION_ACTIVITY)) {
242
                // Initialize user session context
243
                $this->initUserSession();
244
            }
245
246
            // Load plugins
247
            $this->pluginManager->loadPlugins();
248
249
            if ($this->context->isLoggedIn()
250
                && $this->context->getAppStatus() === SessionContext::APP_STATUS_RELOADED
251
            ) {
252
                logger('Reload user profile');
253
                // Recargar los permisos del perfil de usuario
254
                $this->context->setUserProfile(
255
                    $this->container->get(UserProfileService::class)
256
                        ->getById($this->context->getUserData()
257
                            ->getUserProfileId())->getProfile());
258
            }
259
260
            return;
261
        }
262
263
        // Do not keep the PHP's session opened
264
        SessionContext::close();
265
    }
266
267
    /**
268
     * Iniciar la sesión PHP
269
     *
270
     * @param bool $encrypt Encriptar la sesión de PHP
271
     *
272
     * @throws Exception
273
     */
274
    private function initSession($encrypt = false)
275
    {
276
        if ($encrypt === true
277
            && Bootstrap::$checkPhpVersion
278
            && ($key = $this->secureSessionService->getKey(UUIDCookie::factory($this->request))) !== false) {
279
            session_set_save_handler(new CryptSessionHandler($key), true);
280
        }
281
282
283
        try {
284
            $this->context->initialize();
285
        } catch (Exception $e) {
286
            $this->router->response()->header('HTTP/1.1', '500 Internal Server Error');
287
288
            throw $e;
289
        }
290
    }
291
292
    /**
293
     * Comprueba que la aplicación esté instalada
294
     * Esta función comprueba si la aplicación está instalada. Si no lo está, redirige al instalador.
295
     */
296
    private function checkInstalled()
297
    {
298
        return $this->configData->isInstalled()
299
            && $this->router->request()->param('r') !== 'install/index';
300
    }
301
302
    /**
303
     * Comprobar si es necesario actualizar componentes
304
     *
305
     * @throws FileException
306
     */
307
    private function checkUpgrade()
308
    {
309
        UpgradeUtil::fixAppUpgrade($this->configData, $this->config);
310
311
        return $this->configData->getUpgradeKey()
312
            || (UpgradeDatabaseService::needsUpgrade($this->configData->getDatabaseVersion()) ||
313
                UpgradeAppService::needsUpgrade($this->configData->getAppVersion()));
314
    }
315
316
    /**
317
     * Inicializar la sesión de usuario
318
     *
319
     */
320
    private function initUserSession()
321
    {
322
        $lastActivity = $this->context->getLastActivity();
323
        $inMaintenance = $this->configData->isMaintenance();
324
325
        // Session timeout
326
        if ($lastActivity > 0
327
            && !$inMaintenance
328
            && time() > ($lastActivity + $this->getSessionLifeTime())
329
        ) {
330
            if ($this->router->request()->cookies()->get(session_name()) !== null) {
331
                $this->router->response()->cookie(session_name(), '', time() - 42000);
332
            }
333
334
            SessionContext::restart();
335
        } else {
336
            $sidStartTime = $this->context->getSidStartTime();
337
338
            // Regenerate session's ID frequently to avoid fixation
339
            if ($sidStartTime === 0) {
340
                // Try to set PHP's session lifetime
341
                @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

341
                /** @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...
342
            } else if (!$inMaintenance
343
                && time() > ($sidStartTime + SessionContext::MAX_SID_TIME)
344
                && $this->context->isLoggedIn()
345
            ) {
346
                try {
347
                    CryptSession::reKey($this->context);
348
                } catch (CryptoException $e) {
349
                    logger($e->getMessage());
350
351
                    SessionContext::restart();
352
                    return;
353
                }
354
            }
355
356
            $this->context->setLastActivity(time());
357
        }
358
    }
359
360
    /**
361
     * Obtener el timeout de sesión desde la configuración.
362
     *
363
     * @return int con el tiempo en segundos
364
     */
365
    private function getSessionLifeTime()
366
    {
367
        $timeout = $this->context->getSessionTimeout();
368
369
        try {
370
            if ($this->isIndex || $timeout === null) {
371
                $userTimeout = $this->getSessionTimeoutForUser($timeout) ?: $this->configData->getSessionTimeout();
372
373
                logger('Session timeout: ' . $userTimeout);
374
375
                return $this->context->setSessionTimeout($userTimeout);
376
            }
377
        } catch (Exception $e) {
378
            processException($e);
379
        }
380
381
        return $timeout;
382
    }
383
384
    /**
385
     * @param int $default
386
     *
387
     * @return int
388
     * @throws ConstraintException
389
     * @throws InvalidArgumentException
390
     * @throws NoSuchPropertyException
391
     * @throws QueryException
392
     */
393
    private function getSessionTimeoutForUser(int $default = null)
394
    {
395
        if ($this->context->isLoggedIn()) {
396
            $itemPreset = $this->itemPresetService->getForCurrentUser(ItemPresetInterface::ITEM_TYPE_SESSION_TIMEOUT);
397
398
            if ($itemPreset !== null) {
399
                $sessionTimeout = $itemPreset->hydrate(SessionTimeout::class);
400
401
                if (Address::check($this->request->getClientAddress(), $sessionTimeout->getAddress(), $sessionTimeout->getMask())) {
402
                    return $sessionTimeout->getTimeout();
403
                }
404
            }
405
        }
406
407
        return $default;
408
    }
409
}