Test Failed
Push — master ( 0d14a6...e876c1 )
by Fran
09:15
created

Security::init()   B

Complexity

Conditions 6
Paths 32

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 6

Importance

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