Test Failed
Push — master ( f7ffcc...a40e1b )
by Fran
03:18
created

Security::checkAdmin()   C

Complexity

Conditions 11
Paths 26

Size

Total Lines 31
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 132

Importance

Changes 0
Metric Value
cc 11
eloc 20
nc 26
nop 2
dl 0
loc 31
ccs 0
cts 20
cp 0
crap 132
rs 5.2653
c 0
b 0
f 0

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