Completed
Push — master ( 04bd65...4036d4 )
by Fran
09:06
created

Security::getUser()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 2
b 0
f 0
nc 1
nop 0
dl 0
loc 4
ccs 0
cts 2
cp 0
crap 2
rs 10
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
15
        use SingletonTrait;
16
        /**
17
         * @var array $user
18
         */
19
        private $user;
20
21
        /**
22
         * @var array $admin
23
         */
24
        private $admin;
25
26
        private $authorized = FALSE;
27
28
        protected $session;
29
30
        /**
31
         * Constructor por defecto
32
         */
33 2
        public function __construct()
34
        {
35 2
            if (PHP_SESSION_NONE === session_status()) {
36 1
                session_start();
37
            }
38 2
            $this->session = (is_null($_SESSION)) ? array() : $_SESSION;
39 2
            if (NULL === $this->getSessionKey('__FLASH_CLEAR__')) {
40 2
                $this->clearFlashes();
41 2
                $this->setSessionKey('__FLASH_CLEAR__', microtime(TRUE));
42 2
            }
43 2
            $this->user = (array_key_exists(sha1('USER'), $this->session)) ? unserialize($this->session[sha1('USER')]) : NULL;
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 126 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...
44 2
            $this->admin = (array_key_exists(sha1('ADMIN'), $this->session)) ? unserialize($this->session[sha1('ADMIN')]) : NULL;
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 129 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...
45 2
        }
46
47
        /**
48
         * Método estático que devuelve los perfiles de la plataforma
49
         * @return array
50
         */
51
        public static function getProfiles()
52
        {
53
            return array(
54
                sha1('superadmin') => _('Administrador'),
55
                sha1('admin')      => _('Gestor'),
56
            );
57
        }
58
59
        /**
60
         * Método estático que devuelve los perfiles disponibles
61
         * @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...
62
         */
63
        public static function getCleanProfiles()
64
        {
65
            return array(
66
                '__SUPER_ADMIN__' => sha1('superadmin'),
67
                '__ADMIN__'       => sha1('admin'),
68
            );
69
        }
70
71
        /**
72
         * Método que guarda los administradores
73
         *
74
         * @param $user
75
         *
76
         * @return bool
77
         */
78
        public static function save($user)
79
        {
80
            $admins = array();
81 View Code Duplication
            if (file_exists(CONFIG_DIR . DIRECTORY_SEPARATOR . 'admins.json')) {
82
                $admins = json_decode(file_get_contents(CONFIG_DIR . DIRECTORY_SEPARATOR . 'admins.json'), TRUE);
83
            }
84
            $admins[$user['username']]['hash'] = sha1($user['username'] . $user['password']);
85
            $admins[$user['username']]['profile'] = $user['profile'];
86
87
            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 140 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...
88
        }
89
90
        /**
91
         * Servicio que actualiza los datos del usuario
92
         *
93
         * @param $user
94
         */
95
        public function updateUser($user)
96
        {
97
            $this->user = $user;
98
        }
99
100
        /**
101
         * Método que devuelve los administradores de una plataforma
102
         * @return array|mixed
103
         */
104
        public function getAdmins()
105
        {
106
            $admins = array();
107 View Code Duplication
            if (file_exists(CONFIG_DIR . DIRECTORY_SEPARATOR . 'admins.json')) {
108
                $admins = json_decode(file_get_contents(CONFIG_DIR . DIRECTORY_SEPARATOR . 'admins.json'), TRUE);
109
            }
110
111
            return $admins;
112
        }
113
114
        /**
115
         * Método que devuelve si un usuario tiene privilegios para acceder a la zona de administración
116
         *
117
         * @param null $user
118
         * @param null $pass
119
         *
120
         * @return bool
121
         * @throws \HttpException
122
         */
123
        public function checkAdmin($user = NULL, $pass = NULL)
124
        {
125
            Logger::log('Checking admin session');
126
            if (!$this->authorized) {
127
                $request = Request::getInstance();
128
                if (!file_exists(CONFIG_DIR . DIRECTORY_SEPARATOR . 'admins.json')) {
129
                    //Si no hay fichero de usuarios redirigimos directamente al gestor
130
                    return Router::getInstance()->getAdmin()->adminers();
131
                }
132
                $admins = $this->getAdmins();
133
                //Sacamos las credenciales de la petición
134
                $user = $user ?: $request->getServer('PHP_AUTH_USER');
135
                $pass = $pass ?: $request->getServer('PHP_AUTH_PW');
136
                if (NULL === $user || (array_key_exists($user, $admins) && empty($admins[$user]))) {
137
                    list($user, $pass) = $this->getAdminFromCookie();
138
                }
139
                if (!empty($user) && !empty($admins[$user])) {
140
                    $auth = $admins[$user]['hash'];
141
                    $this->authorized = ($auth == sha1($user . $pass));
142
                    $this->admin = array(
143
                        'alias'   => $user,
144
                        'profile' => $admins[$user]['profile'],
145
                    );
146
                }
147
            }
148
149
            return $this->authorized;
150
        }
151
152
        /**
153
         * Método que obtiene el usuario y contraseña de la cookie de sesión de administración
154
         * @return array
155
         */
156
        protected function getAdminFromCookie()
157
        {
158
            $auth_cookie = Request::getInstance()->getCookie($this->getHash());
159
            $user = $pass = array();
160
            if (!empty($auth_cookie)) {
161
                list($user, $pass) = explode(':', base64_decode($auth_cookie));
162
            }
163
164
            return array($user, $pass);
165
        }
166
167
        /**
168
         * Método privado para la generación del hash de la cookie de administración
169
         * @return string
170
         */
171
        public function getHash()
172
        {
173
            return substr(md5('admin'), 0, 8);
174
        }
175
176
        /**
177
         * Método que devuelve el usuario logado
178
         * @return user
0 ignored issues
show
Documentation introduced by
Should the return type not be array? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
179
         */
180
        public function getUser()
181
        {
182
            return $this->user;
183
        }
184
185
        /**
186
         * Método que devuelve el usuario administrador logado
187
         * @return array
188
         */
189
        public function getAdmin()
190
        {
191
            return $this->admin;
192
        }
193
194
        /**
195
         * Método que calcula si se está logado o para acceder a administración
196
         * @return bool
197
         */
198
        public function canAccessRestrictedAdmin()
199
        {
200
            return $this->admin || preg_match('/^\/admin\/login/i', Request::requestUri());
201
        }
202
203
        /**
204
         * Servicio que devuelve una pantalla de error porque se necesita estar authenticado
205
         *
206
         * @param string|null $route
207
         *
208
         * @return string|null
209
         */
210
        public function notAuthorized($route)
211
        {
212
            return Template::getInstance()->render('notauthorized.html.twig', array(
213
                'route' => $route,
214
            ));
215
        }
216
217
        /**
218
         * Servicio que chequea si un usuario es super administrador o no
219
         * @return bool
220
         */
221
        public function isSuperAdmin()
222
        {
223
            $users = $this->getAdmins();
224
            $logged = $this->getAdminFromCookie();
225
            $profiles = Security::getCleanProfiles();
226
            if ($users[$logged[0]]) {
227
                $security = $users[$logged[0]]['profile'];
228
229
                return $profiles['__SUPER_ADMIN__'] === $security;
230
            }
231
232
            return FALSE;
233
        }
234
235
        /**
236
         * Servicio que devuelve un dato de sesión
237
         *
238
         * @param string $key
239
         *
240
         * @return mixed
241
         */
242 1
        public function getSessionKey($key)
243
        {
244 1
            $data = NULL;
245 1
            if (array_key_exists($key, $this->session)) {
246
                $data = $this->session[$key];
247
            }
248
249 1
            return $data;
250
        }
251
252
        /**
253
         * Servicio que setea una variable de sesión
254
         *
255
         * @param string $key
256
         * @param mixed $data
257
         *
258
         * @return Security
259
         */
260 1
        public function setSessionKey($key, $data = NULL)
261
        {
262 1
            $this->session[$key] = $data;
263
264 1
            return $this;
265
        }
266
267
        /**
268
         * Servicio que devuelve los mensajes flash de sesiones
269
         * @return mixed
270
         */
271
        public function getFlashes()
272
        {
273
            $flashes = $this->getSessionKey(sha1('FLASHES'));
274
275
            return (NULL !== $flashes) ? $flashes : array();
276
        }
277
278
        /**
279
         * Servicio que limpia los mensajes flash
280
         * @return $this
281
         */
282 1
        public function clearFlashes()
283
        {
284 1
            $this->setSessionKey(sha1('FLASHES'), NULL);
285
286 1
            return $this;
287
        }
288
289
        /**
290
         * Servicio que inserta un flash en sesión
291
         *
292
         * @param string $key
293
         * @param mixed $data
294
         */
295
        public function setFlash($key, $data = NULL)
296
        {
297
            $flashes = $this->getFlashes();
298
            if (!is_array($flashes)) {
299
                $flashes = array();
300
            }
301
            $flashes[$key] = $data;
302
            $this->setSessionKey(sha1('FLASHES'), $flashes);
303
        }
304
305
        /**
306
         * Servicio que devuelve un flash de sesión
307
         *
308
         * @param string $key
309
         *
310
         * @return mixed
311
         */
312
        public function getFlash($key)
313
        {
314
            $flashes = $this->getFlashes();
315
316
            return (NULL !== $key && array_key_exists($key, $flashes)) ? $flashes[$key] : NULL;
317
        }
318
319
        /**
320
         * Servicio que actualiza
321
         *
322
         * @param boolean $closeSession
323
         *
324
         * @return Security
325
         */
326
        public function updateSession($closeSession = FALSE)
327
        {
328
            Logger::log('Update session');
329
            $_SESSION = $this->session;
330
            $_SESSION[sha1('USER')] = serialize($this->user);
331
            $_SESSION[sha1('ADMIN')] = serialize($this->admin);
332
            if ($closeSession) {
333
                Logger::log('Close session');
334
                session_write_close();
335
            }
336
            Logger::log('Session updated');
337
            return $this;
338
        }
339
340
        /**
341
         * Servicio que limpia la sesión
342
         */
343
        public function closeSession()
344
        {
345
            session_destroy();
346
            session_regenerate_id(TRUE);
347
        }
348
349
        /**
350
         * Extract parts from token
351
         * @param string $token
352
         *
353
         * @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...
354
         */
355
        private static function extractTokenParts($token)
356
        {
357
            $axis = 0;
358
            $parts = array();
359
            try {
360
                $partLength = floor(strlen($token) / 10);
361
                for ($i = 0, $ct = ceil(strlen($token) / $partLength); $i < $ct; $i++) {
362
                    $parts[] = substr($token, $axis, $partLength);
363
                    $axis += $partLength;
364
                }
365
            } catch(\Exception $e) {
366
                $partLength = 0;
367
            }
368
369
            return array($partLength, $parts);
370
        }
371
372
        /**
373
         * Extract Ts and Module from token
374
         * @param array $parts
375
         * @param int $partLength
376
         *
377
         * @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...
378
         */
379
        private static function extractTsAndMod(array &$parts, $partLength)
380
        {
381
            $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...
382
            $mod = '';
383
            foreach ($parts as &$part) {
384
                if (strlen($part) == $partLength) {
385
                    $ts .= substr($part, 0, 1);
386
                    $mod .= substr($part, $partLength - 2, 2);
387
                    $part = substr($part, 1, $partLength - 3);
388
                }
389
            }
390
            return array($ts, $mod);
391
        }
392
393
        /**
394
         * Decode token to check authorized request
395
         * @param string $token
396
         * @param string $module
397
         *
398
         * @return null|string
399
         */
400
        private static function decodeToken($token, $module = 'PSFS')
401
        {
402
            $decoded = NULL;
403
            list($partLength, $parts) = self::extractTokenParts($token);
404
            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...
405
            $hashMod = substr(strtoupper(sha1($module)), strlen($ts) / 2, strlen($ts) * 2);
406
            if (time() - (integer)$ts < 300 && $hashMod === $mod) {
407
                $decoded = implode('', $parts);
408
            }
409
            return $decoded;
410
        }
411
412
        /**
413
         * Generate a authorized token
414
         * @param string $secret
415
         * @param string $module
416
         *
417
         * @return string
418
         */
419
        public static function generateToken($secret, $module = 'PSFS')
420
        {
421
            $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...
422
            $hashModule = substr(strtoupper(sha1($module)), strlen($ts) / 2, strlen($ts) * 2);
423
            $hash = hash('sha256', $secret);
424
            $insert = floor(strlen($hash) / strlen($ts));
425
            $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...
426
            $token = '';
427
            for ($i = 0, $ct = strlen($ts); $i < $ct; $i++) {
428
                $token .= substr($ts, $i, 1) . substr($hash, $j, $insert) . substr($hashModule, $i, 2);
429
                $j += $insert;
430
            }
431
            $token .= substr($hash, ($insert * strlen($ts)), strlen($hash) - ($insert * strlen($ts)));
432
            return $token;
433
        }
434
435
        /**
436
         * Checks if auth token is correct
437
         * @param string $token
438
         * @param string $secret
439
         * @param string $module
440
         *
441
         * @return bool
442
         */
443
        public static function checkToken($token, $secret, $module = 'PSFS')
444
        {
445
            if (0 === strlen($token) || 0 === strlen($secret)) {
446
                return false;
447
            }
448
            $module = strtolower($module);
449
            $decodedToken = self::decodeToken($token, $module);
450
            $expectedToken = self::decodeToken(self::generateToken($secret, $module), $module);
451
452
            return $decodedToken === $expectedToken;
453
        }
454
455
    }
456