Passed
Push — master ( be88e3...87c6b6 )
by Fran
04:17
created

Security::setFlash()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0185

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 2
dl 0
loc 8
ccs 5
cts 6
cp 0.8333
crap 2.0185
rs 10
c 0
b 0
f 0
1
<?php
2
namespace PSFS\base;
3
4
use PSFS\base\exception\ConfigException;
5
use PSFS\base\types\helpers\ResponseHelper;
6
use PSFS\base\types\traits\SecureTrait;
7
use PSFS\base\types\traits\SingletonTrait;
8
9
/**
10
 * Class Security
11
 * @package PSFS
12
 */
13
class Security
14
{
15
    // sha1('user')
16
    const USER_ID_TOKEN = '12dea96fec20593566ab75692c9949596833adc9';
17
    // sha1('admin')
18
    const MANAGER_ID_TOKEN = 'd033e22ae348aeb5660fc2140aec35850c4da997';
19
    // sha1('superadmin')
20
    const ADMIN_ID_TOKEN = '889a3a791b3875cfae413574b53da4bb8a90d53e';
21
    // sha1('FLASHES')
22
    const FLASH_MESSAGE_TOKEN = '4680c68435db1bfbf17c3fcc4f7b39d2c6122504';
23
    const LOGGED_USER_TOKEN = '__U_T_L__';
24
25
    use SecureTrait;
26
    use SingletonTrait;
27
    /**
28
     * @var array $user
29
     */
30
    private $user = null;
31
32
    /**
33
     * @var array $admin
34
     */
35
    private $admin = null;
36
37
    /**
38
     * @var bool $authorized
39
     */
40
    private $authorized = FALSE;
41
42
    /**
43
     * @var bool $checked
44
     */
45
    private $checked = false;
46
47
    /**
48
     * @var array $session
49
     */
50
    protected $session;
51
52
    /**
53
     * Constructor por defecto
54
     */
55 2
    public function init()
56
    {
57 2
        $this->initSession();
58 2
        $this->session = null === $_SESSION ? array() : $_SESSION;
59 2
        if (NULL === $this->getSessionKey('__FLASH_CLEAR__')) {
60 2
            $this->clearFlashes();
61 2
            $this->setSessionKey('__FLASH_CLEAR__', microtime(TRUE));
62
        }
63 2
        $this->user = array_key_exists(self::USER_ID_TOKEN, $this->session) ? unserialize($this->session[self::USER_ID_TOKEN]) : NULL;
64 2
        $this->admin = array_key_exists(self::ADMIN_ID_TOKEN, $this->session) ? unserialize($this->session[self::ADMIN_ID_TOKEN]) : NULL;
65 2
        if (null === $this->admin) {
66 2
            $this->checkAdmin();
67
        }
68 2
        $this->setLoaded(true);
69 2
    }
70
71 2
    private function initSession() {
72 2
        if (PHP_SESSION_NONE === session_status() && !headers_sent()) {
73
            session_start();
74
        }
75
        // Fix for phpunits
76 2
        if(!isset($_SESSION)) {
77 1
            $_SESSION = [];
78
        }
79 2
    }
80
81
    /**
82
     * @return array
83
     */
84 2
    public static function getProfiles()
85
    {
86
        return array(
87 2
            self::ADMIN_ID_TOKEN => _('Administrador'),
88 2
            self::MANAGER_ID_TOKEN => _('Gestor'),
89 2
            self::USER_ID_TOKEN => _('Usuario'),
90
        );
91
    }
92
93
    /**
94
     * @return array
95
     */
96 1
    public function getAdminProfiles()
97
    {
98 1
        return static::getProfiles();
99
    }
100
101
    /**
102
     * @return array
103
     */
104 2
    public static function getCleanProfiles()
105
    {
106
        return array(
107 2
            '__SUPER_ADMIN__' => self::ADMIN_ID_TOKEN,
108 2
            '__ADMIN__' => self::MANAGER_ID_TOKEN,
109 2
            '__USER__' => self::USER_ID_TOKEN,
110
        );
111
    }
112
113
    /**
114
     * @return array
115
     */
116 1
    public function getAdminCleanProfiles()
117
    {
118 1
        return static::getCleanProfiles();
119
    }
120
121
    /**
122
     * @param mixed $user
123
     * @return bool
124
     * @throws exception\GeneratorException
125
     * @throws ConfigException
126
     */
127 1
    public static function save($user)
128
    {
129 1
        $saved = true;
130 1
        $admins = Cache::getInstance()->getDataFromFile(CONFIG_DIR . DIRECTORY_SEPARATOR . 'admins.json', Cache::JSONGZ, true) ?: [];
131 1
        $admins[$user['username']]['hash'] = sha1($user['username'] . $user['password']);
132 1
        $admins[$user['username']]['profile'] = $user['profile'];
133
134 1
        Cache::getInstance()->storeData(CONFIG_DIR . DIRECTORY_SEPARATOR . 'admins.json', $admins, Cache::JSONGZ, true);
135 1
        return $saved;
136
    }
137
138
    /**
139
     * @param mixed $user
140
     * @return bool
141
     * @throws exception\GeneratorException
142
     */
143 1
    public function saveUser($user)
144
    {
145 1
        $saved = false;
146 1
        if (!empty($user)) {
147 1
            $saved = static::save($user);
148
        }
149 1
        return $saved;
150
    }
151
152
    /**
153
     * @param mixed $user
154
     */
155 1
    public function updateUser($user)
156
    {
157 1
        $this->user = $user;
158 1
    }
159
160
    /**
161
     * @param $alias
162
     * @param $profile
163
     */
164 1
    public function updateAdmin($alias, $profile) {
165 1
        $this->admin = array(
166 1
            'alias' => $alias,
167 1
            'profile' => $profile,
168
        );
169 1
        $this->setSessionKey(self::ADMIN_ID_TOKEN, serialize($this->admin));
170 1
    }
171
172
    /**
173
     * @return array|null
174
     */
175 4
    public function getAdmins()
176
    {
177 4
        return Cache::getInstance()->getDataFromFile(CONFIG_DIR . DIRECTORY_SEPARATOR . 'admins.json', Cache::JSONGZ, true);
178
    }
179
180
    /**
181
     * @param string $user
182
     * @param string $pass
183
     * @param boolean $force
184
     *
185
     * @return bool
186
     */
187 3
    public function checkAdmin($user = NULL, $pass = NULL, $force = false)
188
    {
189 3
        Logger::log('Checking admin session');
190 3
        if ((!$this->authorized && !$this->checked) || $force) {
191 3
            $admins = $this->getAdmins();
192 3
            if (null !== $admins) {
193 1
                $request = Request::getInstance();
194
                //Sacamos las credenciales de la petición
195 1
                $user = $user ?: $request->getServer('PHP_AUTH_USER');
196 1
                $pass = $pass ?: $request->getServer('PHP_AUTH_PW');
197 1
                if (NULL === $user || (array_key_exists($user, $admins) && empty($admins[$user]))) {
198 1
                    list($user, $pass) = $this->getAdminFromCookie();
199
                }
200 1
                if (!empty($user) && !empty($admins[$user])) {
201 1
                    $auth = $admins[$user]['hash'];
202 1
                    $this->authorized = ($auth === sha1($user . $pass));
203 1
                    if ($this->authorized) {
204 1
                        $this->updateAdmin($user , $admins[$user]['profile']);
205 1
                        ResponseHelper::setCookieHeaders([
206
                            [
207 1
                                'name' => $this->getHash(),
208 1
                                'value' => base64_encode("$user:$pass"),
209
                                'http' => true,
210 1
                                'domain' => '',
211
                            ]
212
                        ]);
213 1
                        $this->setSessionKey(self::LOGGED_USER_TOKEN, base64_encode("{$user}:{$pass}"));
214
                    }
215
                } else {
216 1
                    $this->admin = null;
217 1
                    $this->setSessionKey(self::ADMIN_ID_TOKEN, null);
218
                }
219 1
                $this->checked = true;
220
            }
221
        }
222
223 3
        return $this->authorized;
224
    }
225
226
    /**
227
     * Método que obtiene el usuario y contraseña de la cookie de sesión de administración
228
     * @return array
229
     */
230 1
    protected function getAdminFromCookie()
231
    {
232 1
        $auth_cookie = Request::getInstance()->getCookie($this->getHash());
233 1
        $user = $pass = array();
234 1
        if (!empty($auth_cookie)) {
235 1
            list($user, $pass) = explode(':', base64_decode($auth_cookie));
236
        }
237
238 1
        return array($user, $pass);
239
    }
240
241
    /**
242
     * Método privado para la generación del hash de la cookie de administración
243
     * @return string
244
     */
245 1
    public function getHash()
246
    {
247 1
        return substr(self::MANAGER_ID_TOKEN, 0, 8);
248
    }
249
250
    /**
251
     * Método que devuelve el usuario logado
252
     * @return array
253
     */
254 2
    public function getUser()
255
    {
256 2
        return $this->user;
257
    }
258
259
    /**
260
     * Método que devuelve el usuario administrador logado
261
     * @return array
262
     */
263 2
    public function getAdmin()
264
    {
265 2
        return $this->admin;
266
    }
267
268
    /**
269
     * Método que calcula si se está logado o para acceder a administración
270
     * @return bool
271
     */
272 3
    public function canAccessRestrictedAdmin()
273
    {
274 3
        return null !== $this->admin && !preg_match('/^\/admin\/login/i', Request::requestUri());
275
    }
276
277
    /**
278
     * Servicio que devuelve una pantalla de error porque se necesita estar authenticado
279
     *
280
     * @param string|null $route
281
     *
282
     * @return string|null
283
     */
284
    public function notAuthorized($route)
285
    {
286
        return Template::getInstance()->render('notauthorized.html.twig', array(
287
            'route' => $route,
288
        ));
289
    }
290
291
    /**
292
     * @return bool
293
     */
294 1
    public function isSuperAdmin()
295
    {
296 1
        $users = $this->getAdmins();
297 1
        $logged = $this->getAdmin();
298 1
        if (is_array($logged)
299 1
            && array_key_exists('alias', $logged)
300 1
            && array_key_exists($logged['alias'], $users)) {
0 ignored issues
show
Bug introduced by
It seems like $users can also be of type null; however, parameter $search of array_key_exists() does only seem to accept array, 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

300
            && array_key_exists($logged['alias'], /** @scrutinizer ignore-type */ $users)) {
Loading history...
301 1
            $security = $users[$logged['alias']]['profile'];
302 1
            return self::ADMIN_ID_TOKEN === $security;
303
        }
304
305
        return FALSE;
306
    }
307
308
    /**
309
     *
310
     * @param string $key
311
     *
312
     * @return mixed
313
     */
314 7
    public function getSessionKey($key)
315
    {
316 7
        $data = NULL;
317 7
        if (array_key_exists($key, $this->session)) {
318 5
            $data = $this->session[$key];
319
        }
320
321 7
        return $data;
322
    }
323
324
    /**
325
     *
326
     * @param string $key
327
     * @param mixed $data
328
     *
329
     * @return Security
330
     */
331 6
    public function setSessionKey($key, $data = NULL)
332
    {
333 6
        $this->session[$key] = $data;
334
335 6
        return $this;
336
    }
337
338
    /**
339
     * @return mixed
340
     */
341 2
    public function getFlashes()
342
    {
343 2
        $flashes = $this->getSessionKey(self::FLASH_MESSAGE_TOKEN);
344
345 2
        return (NULL !== $flashes) ? $flashes : array();
346
    }
347
348
    /**
349
     * @return $this
350
     */
351 3
    public function clearFlashes()
352
    {
353 3
        $this->setSessionKey(self::FLASH_MESSAGE_TOKEN, NULL);
354
355 3
        return $this;
356
    }
357
358
    /**
359
     *
360
     * @param string $key
361
     * @param mixed $data
362
     */
363 1
    public function setFlash($key, $data = NULL)
364
    {
365 1
        $flashes = $this->getFlashes();
366 1
        if (!is_array($flashes)) {
367
            $flashes = [];
368
        }
369 1
        $flashes[$key] = $data;
370 1
        $this->setSessionKey(self::FLASH_MESSAGE_TOKEN, $flashes);
371 1
    }
372
373
    /**
374
     *
375
     * @param string $key
376
     *
377
     * @return mixed
378
     */
379 2
    public function getFlash($key)
380
    {
381 2
        $flashes = $this->getFlashes();
382
383 2
        return (NULL !== $key && array_key_exists($key, $flashes)) ? $flashes[$key] : NULL;
384
    }
385
386
    /**
387
     *
388
     * @param boolean $closeSession
389
     *
390
     * @return Security
391
     */
392 2
    public function updateSession($closeSession = FALSE)
393
    {
394 2
        Logger::log('Update session');
395 2
        $_SESSION = $this->session;
396 2
        $_SESSION[self::USER_ID_TOKEN] = serialize($this->user);
397 2
        $_SESSION[self::ADMIN_ID_TOKEN] = serialize($this->admin);
398 2
        if ($closeSession) {
399 1
            Logger::log('Close session');
400 1
            @session_write_close();
0 ignored issues
show
Bug introduced by
Are you sure the usage of session_write_close() is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Security Best Practice introduced by
It seems like you do not handle an error condition for session_write_close(). 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

400
            /** @scrutinizer ignore-unhandled */ @session_write_close();

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...
401 1
            @session_start();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for session_start(). 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

401
            /** @scrutinizer ignore-unhandled */ @session_start();

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...
402
        }
403 2
        Logger::log('Session updated');
404 2
        return $this;
405
    }
406
407 1
    public function closeSession()
408
    {
409 1
        unset($_SESSION);
410 1
        @session_destroy();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for session_destroy(). 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

410
        /** @scrutinizer ignore-unhandled */ @session_destroy();

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...
411 1
        @session_regenerate_id(TRUE);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for session_regenerate_id(). 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

411
        /** @scrutinizer ignore-unhandled */ @session_regenerate_id(TRUE);

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...
412 1
        @session_start();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for session_start(). 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

412
        /** @scrutinizer ignore-unhandled */ @session_start();

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...
413 1
    }
414
415
}
416