Passed
Push — master ( 84d9fe...0a2888 )
by Fran
04:03
created

Security::setSessionKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 2
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
ccs 3
cts 3
cp 1
crap 1
1
<?php
2
namespace PSFS\base;
3
4
use PSFS\base\types\traits\SecureTrait;
5
use PSFS\base\types\traits\SingletonTrait;
6
7
/**
8
 * Class Security
9
 * @package PSFS
10
 */
11
class Security
12
{
13
    // sha1('user')
14
    const USER_ID_TOKEN = '12dea96fec20593566ab75692c9949596833adc9';
15
    // sha1('admin')
16
    const MANAGER_ID_TOKEN = 'd033e22ae348aeb5660fc2140aec35850c4da997';
17
    // sha1('superadmin')
18
    const ADMIN_ID_TOKEN = '889a3a791b3875cfae413574b53da4bb8a90d53e';
19
    // sha1('FLASHES')
20
    const FLASH_MESSAGE_TOKEN = '4680c68435db1bfbf17c3fcc4f7b39d2c6122504';
21
22
    use SecureTrait;
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 2
    public function __construct()
0 ignored issues
show
Coding Style introduced by
__construct uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
53
    {
54 2
        $this->initSession();
55 2
        $this->session = (is_null($_SESSION)) ? array() : $_SESSION;
56 2
        if (NULL === $this->getSessionKey('__FLASH_CLEAR__')) {
57 2
            $this->clearFlashes();
58 2
            $this->setSessionKey('__FLASH_CLEAR__', microtime(TRUE));
59
        }
60 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...
61 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...
62 2
        if (null === $this->admin) {
63 2
            $this->checkAdmin();
64
        }
65 2
    }
66
67
    /**
68
     * Initializator for SESSION
69
     */
70 2
    private function initSession() {
0 ignored issues
show
Coding Style introduced by
initSession uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
71 2
        if (PHP_SESSION_NONE === session_status() && !headers_sent()) {
72
            session_start();
73
        }
74
        // Fix for phpunits
75 2
        if(!isset($_SESSION)) {
76 1
            $_SESSION = [];
77
        }
78 2
    }
79
80
    /**
81
     * Método estático que devuelve los perfiles de la plataforma
82
     * @return array
83
     */
84 1
    public static function getProfiles()
85
    {
86
        return array(
87 1
            self::ADMIN_ID_TOKEN => _('Administrador'),
88 1
            self::MANAGER_ID_TOKEN => _('Gestor'),
89 1
            self::USER_ID_TOKEN => _('Usuario'),
90
        );
91
    }
92
93
    /**
94
     * Method that returns all the available profiles
95
     * @return array
96
     */
97 1
    public function getAdminProfiles()
98
    {
99 1
        return static::getProfiles();
100
    }
101
102
    /**
103
     * Método estático que devuelve los perfiles disponibles
104
     * @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...
105
     */
106 2
    public static function getCleanProfiles()
107
    {
108
        return array(
109 2
            '__SUPER_ADMIN__' => self::ADMIN_ID_TOKEN,
110 2
            '__ADMIN__' => self::MANAGER_ID_TOKEN,
111 2
            '__USER__' => self::USER_ID_TOKEN,
112
        );
113
    }
114
115
    /**
116
     * Método estático que devuelve los perfiles disponibles
117
     * @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...
118
     */
119 1
    public function getAdminCleanProfiles()
120
    {
121 1
        return static::getCleanProfiles();
122
    }
123
124
    /**
125
     * Método que guarda los administradores
126
     *
127
     * @param array $user
128
     *
129
     * @return bool
130
     */
131 1
    public static function save($user)
132
    {
133 1
        $saved = true;
134
        try {
135 1
            $admins = Cache::getInstance()->getDataFromFile(CONFIG_DIR . DIRECTORY_SEPARATOR . 'admins.json', Cache::JSONGZ, true) ?: [];
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 137 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...
136 1
            $admins[$user['username']]['hash'] = sha1($user['username'] . $user['password']);
137 1
            $admins[$user['username']]['profile'] = $user['profile'];
138
139 1
            Cache::getInstance()->storeData(CONFIG_DIR . DIRECTORY_SEPARATOR . 'admins.json', $admins, Cache::JSONGZ, true);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 124 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...
140
        } catch(\Exception $e) {
141
            Logger::log($e->getMessage(), LOG_ERR);
142
            $saved = false;
143
        }
144 1
        return $saved;
145
    }
146
147
    /**
148
     * Method to save a new admin user
149
     * @param array $user
150
     * @return bool
151
     */
152 1
    public function saveUser($user)
153
    {
154 1
        $saved = false;
155 1
        if (!empty($user)) {
156 1
            $saved = static::save($user);
157
        }
158 1
        return $saved;
159
    }
160
161
    /**
162
     * Servicio que actualiza los datos del usuario
163
     *
164
     * @param $user
165
     */
166 1
    public function updateUser($user)
167
    {
168 1
        $this->user = $user;
169 1
    }
170
171
    /**
172
     * Método que devuelve los administradores de una plataforma
173
     * @return array|null
0 ignored issues
show
Documentation introduced by
Should the return type not be array|string|null? 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...
174
     */
175 3
    public function getAdmins()
176
    {
177 3
        return Cache::getInstance()->getDataFromFile(CONFIG_DIR . DIRECTORY_SEPARATOR . 'admins.json', Cache::JSONGZ, true);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 124 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...
178
    }
179
180
    /**
181
     * Método que devuelve si un usuario tiene privilegios para acceder a la zona de administración
182
     *
183
     * @param null $user
184
     * @param null $pass
185
     * @param boolean $force
186
     *
187
     * @return bool
188
     * @throws \HttpException
189
     */
190 3
    public function checkAdmin($user = NULL, $pass = NULL, $force = false)
191
    {
192 3
        Logger::log('Checking admin session');
193 3
        if ((!$this->authorized && !$this->checked) || $force) {
194 3
            $admins = $this->getAdmins();
195 3
            if (null !== $admins) {
196 1
                $request = Request::getInstance();
197
                //Sacamos las credenciales de la petición
198 1
                $user = $user ?: $request->getServer('PHP_AUTH_USER');
199 1
                $pass = $pass ?: $request->getServer('PHP_AUTH_PW');
200 1
                if (NULL === $user || (array_key_exists($user, $admins) && empty($admins[$user]))) {
201 1
                    list($user, $pass) = $this->getAdminFromCookie();
202
                }
203 1
                if (!empty($user) && !empty($admins[$user])) {
204 1
                    $auth = $admins[$user]['hash'];
205 1
                    $this->authorized = ($auth == sha1($user . $pass));
206 1
                    if ($this->authorized) {
207 1
                        $this->admin = array(
208 1
                            'alias' => $user,
209 1
                            'profile' => $admins[$user]['profile'],
210
                        );
211 1
                        $this->setSessionKey(self::ADMIN_ID_TOKEN, serialize($this->admin));
212
                    }
213
                }
214 1
                $this->checked = true;
215
            }
216
        }
217
218 3
        return $this->authorized;
219
    }
220
221
    /**
222
     * Método que obtiene el usuario y contraseña de la cookie de sesión de administración
223
     * @return array
224
     */
225 1
    protected function getAdminFromCookie()
226
    {
227 1
        $auth_cookie = Request::getInstance()->getCookie($this->getHash());
228 1
        $user = $pass = array();
229 1
        if (!empty($auth_cookie)) {
230 1
            list($user, $pass) = explode(':', base64_decode($auth_cookie));
231
        }
232
233 1
        return array($user, $pass);
234
    }
235
236
    /**
237
     * Método privado para la generación del hash de la cookie de administración
238
     * @return string
239
     */
240 1
    public function getHash()
241
    {
242 1
        return substr(self::MANAGER_ID_TOKEN, 0, 8);
243
    }
244
245
    /**
246
     * Método que devuelve el usuario logado
247
     * @return array
248
     */
249 2
    public function getUser()
250
    {
251 2
        return $this->user;
252
    }
253
254
    /**
255
     * Método que devuelve el usuario administrador logado
256
     * @return array
257
     */
258 2
    public function getAdmin()
259
    {
260 2
        return $this->admin;
261
    }
262
263
    /**
264
     * Método que calcula si se está logado o para acceder a administración
265
     * @return bool
266
     */
267 2
    public function canAccessRestrictedAdmin()
268
    {
269 2
        return null !== $this->admin && !preg_match('/^\/admin\/login/i', Request::requestUri());
270
    }
271
272
    /**
273
     * Servicio que devuelve una pantalla de error porque se necesita estar authenticado
274
     *
275
     * @param string|null $route
276
     *
277
     * @return string|null
278
     */
279
    public function notAuthorized($route)
280
    {
281
        return Template::getInstance()->render('notauthorized.html.twig', array(
282
            'route' => $route,
283
        ));
284
    }
285
286
    /**
287
     * Servicio que chequea si un usuario es super administrador o no
288
     * @return bool
289
     */
290 1
    public function isSuperAdmin()
291
    {
292 1
        $users = $this->getAdmins();
293 1
        $logged = $this->getAdmin();
294 1
        if ($users[$logged['alias']]) {
295 1
            $security = $users[$logged['alias']]['profile'];
296 1
            return self::ADMIN_ID_TOKEN === $security;
297
        }
298
299
        return FALSE;
300
    }
301
302
    /**
303
     * Servicio que devuelve un dato de sesión
304
     *
305
     * @param string $key
306
     *
307
     * @return mixed
308
     */
309 6
    public function getSessionKey($key)
310
    {
311 6
        $data = NULL;
312 6
        if (array_key_exists($key, $this->session)) {
313 4
            $data = $this->session[$key];
314
        }
315
316 6
        return $data;
317
    }
318
319
    /**
320
     * Servicio que setea una variable de sesión
321
     *
322
     * @param string $key
323
     * @param mixed $data
324
     *
325
     * @return Security
326
     */
327 5
    public function setSessionKey($key, $data = NULL)
328
    {
329 5
        $this->session[$key] = $data;
330
331 5
        return $this;
332
    }
333
334
    /**
335
     * Servicio que devuelve los mensajes flash de sesiones
336
     * @return mixed
337
     */
338 2
    public function getFlashes()
339
    {
340 2
        $flashes = $this->getSessionKey(self::FLASH_MESSAGE_TOKEN);
341
342 2
        return (NULL !== $flashes) ? $flashes : array();
343
    }
344
345
    /**
346
     * Servicio que limpia los mensajes flash
347
     * @return $this
348
     */
349 3
    public function clearFlashes()
350
    {
351 3
        $this->setSessionKey(self::FLASH_MESSAGE_TOKEN, NULL);
352
353 3
        return $this;
354
    }
355
356
    /**
357
     * Servicio que inserta un flash en sesión
358
     *
359
     * @param string $key
360
     * @param mixed $data
361
     */
362 1
    public function setFlash($key, $data = NULL)
363
    {
364 1
        $flashes = $this->getFlashes();
365 1
        if (!is_array($flashes)) {
366
            $flashes = array();
367
        }
368 1
        $flashes[$key] = $data;
369 1
        $this->setSessionKey(self::FLASH_MESSAGE_TOKEN, $flashes);
370 1
    }
371
372
    /**
373
     * Servicio que devuelve un flash de sesión
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
     * Servicio que actualiza
388
     *
389
     * @param boolean $closeSession
390
     *
391
     * @return Security
392
     */
393 2
    public function updateSession($closeSession = FALSE)
394
    {
395 2
        Logger::log('Update session');
396 2
        $_SESSION = $this->session;
397 2
        $_SESSION[self::USER_ID_TOKEN] = serialize($this->user);
398 2
        $_SESSION[self::ADMIN_ID_TOKEN] = serialize($this->admin);
399 2
        if ($closeSession) {
400 1
            Logger::log('Close session');
401 1
            @session_write_close();
402 1
            @session_start();
403
        }
404 2
        Logger::log('Session updated');
405 2
        return $this;
406
    }
407
408
    /**
409
     * Servicio que limpia la sesión
410
     */
411 1
    public function closeSession()
0 ignored issues
show
Coding Style introduced by
closeSession uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
412
    {
413 1
        unset($_SESSION);
414 1
        @session_destroy();
415 1
        @session_regenerate_id(TRUE);
416 1
        @session_start();
417 1
    }
418
419
}
420