Completed
Push — master ( c350e4...2fe906 )
by Fran
08:21
created

Security::getSessionKey()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 2
eloc 5
c 3
b 0
f 0
nc 2
nop 1
dl 0
loc 9
ccs 6
cts 6
cp 1
crap 2
rs 9.6666
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;
28
29
    /**
30
     * @var array $admin
31
     */
32
    private $admin;
33
34
    private $authorized = FALSE;
35
36
    protected $session;
37
38
    /**
39
     * Constructor por defecto
40
     */
41 2
    public function __construct()
42
    {
43 2
        if (PHP_SESSION_NONE === session_status()) {
44 1
            session_start();
45
        }
46 2
        $this->session = (is_null($_SESSION)) ? array() : $_SESSION;
47 2
        if (NULL === $this->getSessionKey('__FLASH_CLEAR__')) {
48 2
            $this->clearFlashes();
49 2
            $this->setSessionKey('__FLASH_CLEAR__', microtime(TRUE));
50 2
        }
51 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...
52 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...
53 2
    }
54
55
    /**
56
     * Método estático que devuelve los perfiles de la plataforma
57
     * @return array
58
     */
59
    public static function getProfiles()
60
    {
61
        return array(
62
            self::ADMIN_ID_TOKEN => _('Administrador'),
63
            self::MANAGER_ID_TOKEN => _('Gestor'),
64
            self::USER_ID_TOKEN => _('Usuario'),
65
        );
66
    }
67
68
    /**
69
     * Method that returns all the available profiles
70
     */
71
    public function getAdminProfiles()
72
    {
73
        static::getProfiles();
74
    }
75
76
    /**
77
     * Método estático que devuelve los perfiles disponibles
78
     * @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...
79
     */
80
    public static function getCleanProfiles()
81
    {
82
        return array(
83
            '__SUPER_ADMIN__' => self::ADMIN_ID_TOKEN,
84
            '__ADMIN__' => self::MANAGER_ID_TOKEN,
85
            '__USER__' => self::USER_ID_TOKEN,
86
        );
87
    }
88
89
    /**
90
     * Método estático que devuelve los perfiles disponibles
91
     * @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...
92
     */
93
    public function getAdminCleanProfiles()
94
    {
95
        return static::getCleanProfiles();
96
    }
97
98
    /**
99
     * Método que guarda los administradores
100
     *
101
     * @param array $user
102
     *
103
     * @return bool
104
     */
105
    public static function save($user)
106
    {
107
        $admins = array();
108 View Code Duplication
        if (file_exists(CONFIG_DIR . DIRECTORY_SEPARATOR . 'admins.json')) {
109
            $admins = json_decode(file_get_contents(CONFIG_DIR . DIRECTORY_SEPARATOR . 'admins.json'), TRUE);
110
        }
111
        $admins[$user['username']]['hash'] = sha1($user['username'] . $user['password']);
112
        $admins[$user['username']]['profile'] = $user['profile'];
113
114
        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...
115
    }
116
117
    /**
118
     * Method to save a new admin user
119
     * @param array $user
120
     * @return bool
121
     */
122
    public function saveUser($user)
123
    {
124
        $saved = false;
125
        if (!empty($user)) {
126
            $saved = static::save($user);
127
        }
128
        return $saved;
129
    }
130
131
    /**
132
     * Servicio que actualiza los datos del usuario
133
     *
134
     * @param $user
135
     */
136
    public function updateUser($user)
137
    {
138
        $this->user = $user;
139
    }
140
141
    /**
142
     * Método que devuelve los administradores de una plataforma
143
     * @return array|mixed
144
     */
145
    public function getAdmins()
146
    {
147
        $admins = array();
148 View Code Duplication
        if (file_exists(CONFIG_DIR . DIRECTORY_SEPARATOR . 'admins.json')) {
149
            $admins = json_decode(file_get_contents(CONFIG_DIR . DIRECTORY_SEPARATOR . 'admins.json'), TRUE);
150
        }
151
152
        return $admins;
153
    }
154
155
    /**
156
     * Método que devuelve si un usuario tiene privilegios para acceder a la zona de administración
157
     *
158
     * @param null $user
159
     * @param null $pass
160
     *
161
     * @return bool
162
     * @throws \HttpException
163
     */
164
    public function checkAdmin($user = NULL, $pass = NULL)
165
    {
166
        Logger::log('Checking admin session');
167
        if (!$this->authorized) {
168
            $request = Request::getInstance();
169
            if (!file_exists(CONFIG_DIR . DIRECTORY_SEPARATOR . 'admins.json')) {
170
                //Si no hay fichero de usuarios redirigimos directamente al gestor
171
                return Router::getInstance()->getAdmin()->adminers();
172
            }
173
            $admins = $this->getAdmins();
174
            //Sacamos las credenciales de la petición
175
            $user = $user ?: $request->getServer('PHP_AUTH_USER');
176
            $pass = $pass ?: $request->getServer('PHP_AUTH_PW');
177
            if (NULL === $user || (array_key_exists($user, $admins) && empty($admins[$user]))) {
178
                list($user, $pass) = $this->getAdminFromCookie();
179
            }
180
            if (!empty($user) && !empty($admins[$user])) {
181
                $auth = $admins[$user]['hash'];
182
                $this->authorized = ($auth == sha1($user . $pass));
183
                $this->admin = array(
184
                    'alias' => $user,
185
                    'profile' => $admins[$user]['profile'],
186
                );
187
            }
188
        }
189
190
        return $this->authorized;
191
    }
192
193
    /**
194
     * Método que obtiene el usuario y contraseña de la cookie de sesión de administración
195
     * @return array
196
     */
197
    protected function getAdminFromCookie()
198
    {
199
        $auth_cookie = Request::getInstance()->getCookie($this->getHash());
200
        $user = $pass = array();
201
        if (!empty($auth_cookie)) {
202
            list($user, $pass) = explode(':', base64_decode($auth_cookie));
203
        }
204
205
        return array($user, $pass);
206
    }
207
208
    /**
209
     * Método privado para la generación del hash de la cookie de administración
210
     * @return string
211
     */
212
    public function getHash()
213
    {
214
        return substr(md5('admin'), 0, 8);
215
    }
216
217
    /**
218
     * Método que devuelve el usuario logado
219
     * @return array
220
     */
221
    public function getUser()
222
    {
223
        return $this->user;
224
    }
225
226
    /**
227
     * Método que devuelve el usuario administrador logado
228
     * @return array
229
     */
230
    public function getAdmin()
231
    {
232
        return $this->admin;
233
    }
234
235
    /**
236
     * Método que calcula si se está logado o para acceder a administración
237
     * @return bool
238
     */
239
    public function canAccessRestrictedAdmin()
240
    {
241
        return $this->admin || preg_match('/^\/admin\/login/i', Request::requestUri());
242
    }
243
244
    /**
245
     * Servicio que devuelve una pantalla de error porque se necesita estar authenticado
246
     *
247
     * @param string|null $route
248
     *
249
     * @return string|null
250
     */
251
    public function notAuthorized($route)
252
    {
253
        return Template::getInstance()->render('notauthorized.html.twig', array(
254
            'route' => $route,
255
        ));
256
    }
257
258
    /**
259
     * Servicio que chequea si un usuario es super administrador o no
260
     * @return bool
261
     */
262
    public function isSuperAdmin()
263
    {
264
        $users = $this->getAdmins();
265
        $logged = $this->getAdminFromCookie();
266
        $profiles = Security::getCleanProfiles();
267
        if ($users[$logged[0]]) {
268
            $security = $users[$logged[0]]['profile'];
269
270
            return $profiles['__SUPER_ADMIN__'] === $security;
271
        }
272
273
        return FALSE;
274
    }
275
276
    /**
277
     * Servicio que devuelve un dato de sesión
278
     *
279
     * @param string $key
280
     *
281
     * @return mixed
282
     */
283 2
    public function getSessionKey($key)
284
    {
285 2
        $data = NULL;
286 2
        if (array_key_exists($key, $this->session)) {
287 1
            $data = $this->session[$key];
288 1
        }
289
290 2
        return $data;
291
    }
292
293
    /**
294
     * Servicio que setea una variable de sesión
295
     *
296
     * @param string $key
297
     * @param mixed $data
298
     *
299
     * @return Security
300
     */
301 2
    public function setSessionKey($key, $data = NULL)
302
    {
303 2
        $this->session[$key] = $data;
304
305 2
        return $this;
306
    }
307
308
    /**
309
     * Servicio que devuelve los mensajes flash de sesiones
310
     * @return mixed
311
     */
312
    public function getFlashes()
313
    {
314
        $flashes = $this->getSessionKey(self::FLASH_MESSAGE_TOKEN);
315
316
        return (NULL !== $flashes) ? $flashes : array();
317
    }
318
319
    /**
320
     * Servicio que limpia los mensajes flash
321
     * @return $this
322
     */
323 1
    public function clearFlashes()
324
    {
325 1
        $this->setSessionKey(self::FLASH_MESSAGE_TOKEN, NULL);
326
327 1
        return $this;
328
    }
329
330
    /**
331
     * Servicio que inserta un flash en sesión
332
     *
333
     * @param string $key
334
     * @param mixed $data
335
     */
336
    public function setFlash($key, $data = NULL)
337
    {
338
        $flashes = $this->getFlashes();
339
        if (!is_array($flashes)) {
340
            $flashes = array();
341
        }
342
        $flashes[$key] = $data;
343
        $this->setSessionKey(self::FLASH_MESSAGE_TOKEN, $flashes);
344
    }
345
346
    /**
347
     * Servicio que devuelve un flash de sesión
348
     *
349
     * @param string $key
350
     *
351
     * @return mixed
352
     */
353
    public function getFlash($key)
354
    {
355
        $flashes = $this->getFlashes();
356
357
        return (NULL !== $key && array_key_exists($key, $flashes)) ? $flashes[$key] : NULL;
358
    }
359
360
    /**
361
     * Servicio que actualiza
362
     *
363
     * @param boolean $closeSession
364
     *
365
     * @return Security
366
     */
367
    public function updateSession($closeSession = FALSE)
368
    {
369
        Logger::log('Update session');
370
        $_SESSION = $this->session;
371
        $_SESSION[self::USER_ID_TOKEN] = serialize($this->user);
372
        $_SESSION[self::ADMIN_ID_TOKEN] = serialize($this->admin);
373
        if ($closeSession) {
374
            Logger::log('Close session');
375
            session_write_close();
376
        }
377
        Logger::log('Session updated');
378
        return $this;
379
    }
380
381
    /**
382
     * Servicio que limpia la sesión
383
     */
384
    public function closeSession()
385
    {
386
        session_destroy();
387
        session_regenerate_id(TRUE);
388
    }
389
390
    /**
391
     * Extract parts from token
392
     * @param string $token
393
     *
394
     * @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...
395
     */
396
    private static function extractTokenParts($token)
397
    {
398
        $axis = 0;
399
        $parts = array();
400
        try {
401
            $partLength = floor(strlen($token) / 10);
402
            for ($i = 0, $ct = ceil(strlen($token) / $partLength); $i < $ct; $i++) {
403
                $parts[] = substr($token, $axis, $partLength);
404
                $axis += $partLength;
405
            }
406
        } catch (\Exception $e) {
407
            $partLength = 0;
408
        }
409
410
        return array($partLength, $parts);
411
    }
412
413
    /**
414
     * Extract Ts and Module from token
415
     * @param array $parts
416
     * @param int $partLength
417
     *
418
     * @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...
419
     */
420
    private static function extractTsAndMod(array &$parts, $partLength)
421
    {
422
        $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...
423
        $mod = '';
424
        foreach ($parts as &$part) {
425
            if (strlen($part) == $partLength) {
426
                $ts .= substr($part, 0, 1);
427
                $mod .= substr($part, $partLength - 2, 2);
428
                $part = substr($part, 1, $partLength - 3);
429
            }
430
        }
431
        return array($ts, $mod);
432
    }
433
434
    /**
435
     * Decode token to check authorized request
436
     * @param string $token
437
     * @param string $module
438
     *
439
     * @return null|string
440
     */
441
    private static function decodeToken($token, $module = 'PSFS')
442
    {
443
        $decoded = NULL;
444
        list($partLength, $parts) = self::extractTokenParts($token);
445
        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...
446
        $hashMod = substr(strtoupper(sha1($module)), strlen($ts) / 2, strlen($ts) * 2);
447
        if (time() - (integer)$ts < 300 && $hashMod === $mod) {
448
            $decoded = implode('', $parts);
449
        }
450
        return $decoded;
451
    }
452
453
    /**
454
     * Generate a authorized token
455
     * @param string $secret
456
     * @param string $module
457
     *
458
     * @return string
459
     */
460
    public static function generateToken($secret, $module = 'PSFS')
461
    {
462
        $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...
463
        $hashModule = substr(strtoupper(sha1($module)), strlen($ts) / 2, strlen($ts) * 2);
464
        $hash = hash('sha256', $secret);
465
        $insert = floor(strlen($hash) / strlen($ts));
466
        $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...
467
        $token = '';
468
        for ($i = 0, $ct = strlen($ts); $i < $ct; $i++) {
469
            $token .= substr($ts, $i, 1) . substr($hash, $j, $insert) . substr($hashModule, $i, 2);
470
            $j += $insert;
471
        }
472
        $token .= substr($hash, ($insert * strlen($ts)), strlen($hash) - ($insert * strlen($ts)));
473
        return $token;
474
    }
475
476
    /**
477
     * Checks if auth token is correct
478
     * @param string $token
479
     * @param string $secret
480
     * @param string $module
481
     *
482
     * @return bool
483
     */
484
    public static function checkToken($token, $secret, $module = 'PSFS')
485
    {
486
        if (0 === strlen($token) || 0 === strlen($secret)) {
487
            return false;
488
        }
489
        $module = strtolower($module);
490
        $decodedToken = self::decodeToken($token, $module);
491
        $expectedToken = self::decodeToken(self::generateToken($secret, $module), $module);
492
493
        return $decodedToken === $expectedToken;
494
    }
495
496
}
497