Test Failed
Push — master ( 875018...84d9fe )
by Fran
03:57
created

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