Passed
Push — master ( e9d973...038fa0 )
by Fran
03:28
created

Security::updateSession()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 10
nc 2
nop 1
dl 0
loc 13
rs 9.4285
c 0
b 0
f 0
ccs 0
cts 11
cp 0
crap 6
1
<?php
2
3
namespace PSFS\base;
4
5
use PSFS\base\types\SingletonTrait;
6
7
8
/**
9
 * Class Security
10
 * @package PSFS
11
 */
12
class Security
13
{
14
    // sha1('user')
15
    const USER_ID_TOKEN = '12dea96fec20593566ab75692c9949596833adc9';
16
    // sha1('admin')
17
    const MANAGER_ID_TOKEN = 'd033e22ae348aeb5660fc2140aec35850c4da997';
18
    // sha1('superadmin')
19
    const ADMIN_ID_TOKEN = '889a3a791b3875cfae413574b53da4bb8a90d53e';
20
    // sha1('FLASHES')
21
    const FLASH_MESSAGE_TOKEN = '4680c68435db1bfbf17c3fcc4f7b39d2c6122504';
22
23
    use SingletonTrait;
24
    /**
25
     * @var array $user
26
     */
27
    private $user = null;
28
29
    /**
30
     * @var array $admin
31
     */
32
    private $admin = null;
33
34
    /**
35
     * @var bool $authorized
36
     */
37
    private $authorized = FALSE;
38
39
    /**
40
     * @var bool $checked
41
     */
42
    private $checked = false;
43
44
    /**
45
     * @var array $session
46
     */
47
    protected $session;
48
49
    /**
50
     * Constructor por defecto
51
     */
52 3
    public function __construct()
53
    {
54 3
        if (PHP_SESSION_NONE === session_status()) {
55 1
            session_start();
56
        }
57 2
        $this->session = (is_null($_SESSION)) ? array() : $_SESSION;
58 2
        if (NULL === $this->getSessionKey('__FLASH_CLEAR__')) {
59 2
            $this->clearFlashes();
60 2
            $this->setSessionKey('__FLASH_CLEAR__', microtime(TRUE));
61 2
        }
62 2
        $this->user = (array_key_exists(self::USER_ID_TOKEN, $this->session)) ? unserialize($this->session[self::USER_ID_TOKEN]) : NULL;
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 136 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
63 2
        $this->admin = (array_key_exists(self::ADMIN_ID_TOKEN, $this->session)) ? unserialize($this->session[self::ADMIN_ID_TOKEN]) : NULL;
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 139 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
64 2
        if(null === $this->admin) {
65 2
            $this->checkAdmin();
66 2
        }
67 2
    }
68
69
    /**
70
     * Método estático que devuelve los perfiles de la plataforma
71
     * @return array
72
     */
73 1
    public static function getProfiles()
74
    {
75
        return array(
76 1
            self::ADMIN_ID_TOKEN => _('Administrador'),
77 1
            self::MANAGER_ID_TOKEN => _('Gestor'),
78 1
            self::USER_ID_TOKEN => _('Usuario'),
79 1
        );
80
    }
81
82
    /**
83
     * Method that returns all the available profiles
84
     * @return array
85
     */
86
    public function getAdminProfiles()
87
    {
88
        return static::getProfiles();
89
    }
90
91
    /**
92
     * Método estático que devuelve los perfiles disponibles
93
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
94
     */
95
    public static function getCleanProfiles()
96
    {
97
        return array(
98
            '__SUPER_ADMIN__' => self::ADMIN_ID_TOKEN,
99
            '__ADMIN__' => self::MANAGER_ID_TOKEN,
100
            '__USER__' => self::USER_ID_TOKEN,
101
        );
102
    }
103
104
    /**
105
     * Método estático que devuelve los perfiles disponibles
106
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
107
     */
108
    public function getAdminCleanProfiles()
109
    {
110
        return static::getCleanProfiles();
111
    }
112
113
    /**
114
     * Método que guarda los administradores
115
     *
116
     * @param array $user
117
     *
118
     * @return bool
119
     */
120
    public static function save($user)
121
    {
122
        $admins = array();
123 View Code Duplication
        if (file_exists(CONFIG_DIR . DIRECTORY_SEPARATOR . 'admins.json')) {
124
            $admins = json_decode(file_get_contents(CONFIG_DIR . DIRECTORY_SEPARATOR . 'admins.json'), TRUE);
125
        }
126
        $admins[$user['username']]['hash'] = sha1($user['username'] . $user['password']);
127
        $admins[$user['username']]['profile'] = $user['profile'];
128
129
        return (FALSE !== file_put_contents(CONFIG_DIR . DIRECTORY_SEPARATOR . 'admins.json', json_encode($admins, JSON_PRETTY_PRINT)));
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 136 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
130
    }
131
132
    /**
133
     * Method to save a new admin user
134
     * @param array $user
135
     * @return bool
136
     */
137
    public function saveUser($user)
138
    {
139
        $saved = false;
140
        if (!empty($user)) {
141
            $saved = static::save($user);
142
        }
143
        return $saved;
144
    }
145
146
    /**
147
     * Servicio que actualiza los datos del usuario
148
     *
149
     * @param $user
150
     */
151
    public function updateUser($user)
152
    {
153
        $this->user = $user;
154
    }
155
156
    /**
157
     * Método que devuelve los administradores de una plataforma
158
     * @return array|mixed
159
     */
160 1
    public function getAdmins()
161
    {
162 1
        $admins = array();
163 1 View Code Duplication
        if (file_exists(CONFIG_DIR . DIRECTORY_SEPARATOR . 'admins.json')) {
164
            $admins = json_decode(file_get_contents(CONFIG_DIR . DIRECTORY_SEPARATOR . 'admins.json'), TRUE);
165
        }
166
167 1
        return $admins;
168
    }
169
170
    /**
171
     * Método que devuelve si un usuario tiene privilegios para acceder a la zona de administración
172
     *
173
     * @param null $user
174
     * @param null $pass
175
     *
176
     * @return bool
177
     * @throws \HttpException
178
     */
179 1
    public function checkAdmin($user = NULL, $pass = NULL)
180
    {
181 1
        Logger::log('Checking admin session');
182 1
        if (!$this->authorized && !$this->checked) {
183 1
            if (file_exists(CONFIG_DIR . DIRECTORY_SEPARATOR . 'admins.json')) {
184
                $request = Request::getInstance();
185
                $admins = $this->getAdmins();
186
                //Sacamos las credenciales de la petición
187
                $user = $user ?: $request->getServer('PHP_AUTH_USER');
188
                $pass = $pass ?: $request->getServer('PHP_AUTH_PW');
189
                if (NULL === $user || (array_key_exists($user, $admins) && empty($admins[$user]))) {
190
                    list($user, $pass) = $this->getAdminFromCookie();
191
                }
192
                if (!empty($user) && !empty($admins[$user])) {
193
                    $auth = $admins[$user]['hash'];
194
                    $this->authorized = ($auth == sha1($user . $pass));
195
                    if($this->authorized) {
196
                        $this->admin = array(
197
                            'alias' => $user,
198
                            'profile' => $admins[$user]['profile'],
199
                        );
200
                        $this->setSessionKey(self::ADMIN_ID_TOKEN, serialize($this->admin));
201
                    }
202
                }
203
            }
204 1
            $this->checked = true;
205 1
        }
206
207 1
        return $this->authorized;
208
    }
209
210
    /**
211
     * Método que obtiene el usuario y contraseña de la cookie de sesión de administración
212
     * @return array
213
     */
214
    protected function getAdminFromCookie()
215
    {
216
        $auth_cookie = Request::getInstance()->getCookie($this->getHash());
217
        $user = $pass = array();
218
        if (!empty($auth_cookie)) {
219
            list($user, $pass) = explode(':', base64_decode($auth_cookie));
220
        }
221
222
        return array($user, $pass);
223
    }
224
225
    /**
226
     * Método privado para la generación del hash de la cookie de administración
227
     * @return string
228
     */
229
    public function getHash()
230
    {
231
        return substr(md5('admin'), 0, 8);
232
    }
233
234
    /**
235
     * Método que devuelve el usuario logado
236
     * @return array
237
     */
238
    public function getUser()
239
    {
240
        return $this->user;
241
    }
242
243
    /**
244
     * Método que devuelve el usuario administrador logado
245
     * @return array
246
     */
247
    public function getAdmin()
248
    {
249
        return $this->admin;
250
    }
251
252
    /**
253
     * Método que calcula si se está logado o para acceder a administración
254
     * @return bool
255
     */
256 1
    public function canAccessRestrictedAdmin()
257
    {
258 1
        return null !== $this->admin || false != preg_match('/^\/admin\/login/i', Request::requestUri());
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('/^\\/admin\\...\Request::requestUri()) of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
259
    }
260
261
    /**
262
     * Servicio que devuelve una pantalla de error porque se necesita estar authenticado
263
     *
264
     * @param string|null $route
265
     *
266
     * @return string|null
267
     */
268
    public function notAuthorized($route)
269
    {
270
        return Template::getInstance()->render('notauthorized.html.twig', array(
271
            'route' => $route,
272
        ));
273
    }
274
275
    /**
276
     * Servicio que chequea si un usuario es super administrador o no
277
     * @return bool
278
     */
279
    public function isSuperAdmin()
280
    {
281
        $users = $this->getAdmins();
282
        $logged = $this->getAdminFromCookie();
283
        $profiles = Security::getCleanProfiles();
284
        if ($users[$logged[0]]) {
285
            $security = $users[$logged[0]]['profile'];
286
287
            return $profiles['__SUPER_ADMIN__'] === $security;
288
        }
289
290
        return FALSE;
291
    }
292
293
    /**
294
     * Servicio que devuelve un dato de sesión
295
     *
296
     * @param string $key
297
     *
298
     * @return mixed
299
     */
300 2
    public function getSessionKey($key)
301
    {
302 2
        $data = NULL;
303 2
        if (array_key_exists($key, $this->session)) {
304 1
            $data = $this->session[$key];
305 1
        }
306
307 2
        return $data;
308
    }
309
310
    /**
311
     * Servicio que setea una variable de sesión
312
     *
313
     * @param string $key
314
     * @param mixed $data
315
     *
316
     * @return Security
317
     */
318 2
    public function setSessionKey($key, $data = NULL)
319
    {
320 2
        $this->session[$key] = $data;
321
322 2
        return $this;
323
    }
324
325
    /**
326
     * Servicio que devuelve los mensajes flash de sesiones
327
     * @return mixed
328
     */
329
    public function getFlashes()
330
    {
331
        $flashes = $this->getSessionKey(self::FLASH_MESSAGE_TOKEN);
332
333
        return (NULL !== $flashes) ? $flashes : array();
334
    }
335
336
    /**
337
     * Servicio que limpia los mensajes flash
338
     * @return $this
339
     */
340 1
    public function clearFlashes()
341
    {
342 1
        $this->setSessionKey(self::FLASH_MESSAGE_TOKEN, NULL);
343
344 1
        return $this;
345
    }
346
347
    /**
348
     * Servicio que inserta un flash en sesión
349
     *
350
     * @param string $key
351
     * @param mixed $data
352
     */
353
    public function setFlash($key, $data = NULL)
354
    {
355
        $flashes = $this->getFlashes();
356
        if (!is_array($flashes)) {
357
            $flashes = array();
358
        }
359
        $flashes[$key] = $data;
360
        $this->setSessionKey(self::FLASH_MESSAGE_TOKEN, $flashes);
361
    }
362
363
    /**
364
     * Servicio que devuelve un flash de sesión
365
     *
366
     * @param string $key
367
     *
368
     * @return mixed
369
     */
370
    public function getFlash($key)
371
    {
372
        $flashes = $this->getFlashes();
373
374
        return (NULL !== $key && array_key_exists($key, $flashes)) ? $flashes[$key] : NULL;
375
    }
376
377
    /**
378
     * Servicio que actualiza
379
     *
380
     * @param boolean $closeSession
381
     *
382
     * @return Security
383
     */
384
    public function updateSession($closeSession = FALSE)
385
    {
386
        Logger::log('Update session');
387
        $_SESSION = $this->session;
388
        $_SESSION[self::USER_ID_TOKEN] = serialize($this->user);
389
        $_SESSION[self::ADMIN_ID_TOKEN] = serialize($this->admin);
390
        if ($closeSession) {
391
            Logger::log('Close session');
392
            session_write_close();
393
        }
394
        Logger::log('Session updated');
395
        return $this;
396
    }
397
398
    /**
399
     * Servicio que limpia la sesión
400
     */
401
    public function closeSession()
402
    {
403
        session_destroy();
404
        session_regenerate_id(TRUE);
405
    }
406
407
    /**
408
     * Extract parts from token
409
     * @param string $token
410
     *
411
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<double|integer|array>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
412
     */
413
    private static function extractTokenParts($token)
414
    {
415
        $axis = 0;
416
        $parts = array();
417
        try {
418
            $partLength = floor(strlen($token) / 10);
419
            for ($i = 0, $ct = ceil(strlen($token) / $partLength); $i < $ct; $i++) {
420
                $parts[] = substr($token, $axis, $partLength);
421
                $axis += $partLength;
422
            }
423
        } catch (\Exception $e) {
424
            $partLength = 0;
425
        }
426
427
        return array($partLength, $parts);
428
    }
429
430
    /**
431
     * Extract Ts and Module from token
432
     * @param array $parts
433
     * @param int $partLength
434
     *
435
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string[].

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
436
     */
437
    private static function extractTsAndMod(array &$parts, $partLength)
438
    {
439
        $ts = '';
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ts. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
440
        $mod = '';
441
        foreach ($parts as &$part) {
442
            if (strlen($part) == $partLength) {
443
                $ts .= substr($part, 0, 1);
444
                $mod .= substr($part, $partLength - 2, 2);
445
                $part = substr($part, 1, $partLength - 3);
446
            }
447
        }
448
        return array($ts, $mod);
449
    }
450
451
    /**
452
     * Decode token to check authorized request
453
     * @param string $token
454
     * @param string $module
455
     *
456
     * @return null|string
457
     */
458
    private static function decodeToken($token, $module = 'PSFS')
459
    {
460
        $decoded = NULL;
461
        list($partLength, $parts) = self::extractTokenParts($token);
462
        list($ts, $mod) = self::extractTsAndMod($parts, $partLength);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ts. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
463
        $hashMod = substr(strtoupper(sha1($module)), strlen($ts) / 2, strlen($ts) * 2);
464
        if (time() - (integer)$ts < 300 && $hashMod === $mod) {
465
            $decoded = implode('', $parts);
466
        }
467
        return $decoded;
468
    }
469
470
    /**
471
     * Generate a authorized token
472
     * @param string $secret
473
     * @param string $module
474
     *
475
     * @return string
476
     */
477
    public static function generateToken($secret, $module = 'PSFS')
478
    {
479
        $ts = time();
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ts. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
480
        $hashModule = substr(strtoupper(sha1($module)), strlen($ts) / 2, strlen($ts) * 2);
481
        $hash = hash('sha256', $secret);
482
        $insert = floor(strlen($hash) / strlen($ts));
483
        $j = 0;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $j. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
484
        $token = '';
485
        for ($i = 0, $ct = strlen($ts); $i < $ct; $i++) {
486
            $token .= substr($ts, $i, 1) . substr($hash, $j, $insert) . substr($hashModule, $i, 2);
487
            $j += $insert;
488
        }
489
        $token .= substr($hash, ($insert * strlen($ts)), strlen($hash) - ($insert * strlen($ts)));
490
        return $token;
491
    }
492
493
    /**
494
     * Checks if auth token is correct
495
     * @param string $token
496
     * @param string $secret
497
     * @param string $module
498
     *
499
     * @return bool
500
     */
501
    public static function checkToken($token, $secret, $module = 'PSFS')
502
    {
503
        if (0 === strlen($token) || 0 === strlen($secret)) {
504
            return false;
505
        }
506
        $module = strtolower($module);
507
        $decodedToken = self::decodeToken($token, $module);
508
        $expectedToken = self::decodeToken(self::generateToken($secret, $module), $module);
509
510
        return $decodedToken === $expectedToken;
511
    }
512
513
}
514