Passed
Push — master ( 6df67e...d71c1b )
by Fran
03:24
created

Security::getAdminCleanProfiles()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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