Passed
Push — master ( e876c1...0991d4 )
by Fran
03:32
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 1
    public static function getProfiles()
86
    {
87
        return array(
88 1
            self::ADMIN_ID_TOKEN => _('Administrador'),
89 1
            self::MANAGER_ID_TOKEN => _('Gestor'),
90 1
            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 3
    public function getAdmins()
177
    {
178 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...
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
                }
215 1
                $this->checked = true;
216
            }
217
        }
218
219 3
        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
     */
226 1
    protected function getAdminFromCookie()
227
    {
228 1
        $auth_cookie = Request::getInstance()->getCookie($this->getHash());
229 1
        $user = $pass = array();
230 1
        if (!empty($auth_cookie)) {
231 1
            list($user, $pass) = explode(':', base64_decode($auth_cookie));
232
        }
233
234 1
        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
     */
241 1
    public function getHash()
242
    {
243 1
        return substr(self::MANAGER_ID_TOKEN, 0, 8);
244
    }
245
246
    /**
247
     * Método que devuelve el usuario logado
248
     * @return array
249
     */
250 2
    public function getUser()
251
    {
252 2
        return $this->user;
253
    }
254
255
    /**
256
     * Método que devuelve el usuario administrador logado
257
     * @return array
258
     */
259 2
    public function getAdmin()
260
    {
261 2
        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
     */
268 1
    public function canAccessRestrictedAdmin()
269
    {
270 1
        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
     */
291 1
    public function isSuperAdmin()
292
    {
293 1
        $users = $this->getAdmins();
294 1
        $logged = $this->getAdmin();
295 1
        if ($users[$logged['alias']]) {
296 1
            $security = $users[$logged['alias']]['profile'];
297 1
            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
     */
310 6
    public function getSessionKey($key)
311
    {
312 6
        $data = NULL;
313 6
        if (array_key_exists($key, $this->session)) {
314 4
            $data = $this->session[$key];
315
        }
316
317 6
        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
     */
328 5
    public function setSessionKey($key, $data = NULL)
329
    {
330 5
        $this->session[$key] = $data;
331
332 5
        return $this;
333
    }
334
335
    /**
336
     * Servicio que devuelve los mensajes flash de sesiones
337
     * @return mixed
338
     */
339 2
    public function getFlashes()
340
    {
341 2
        $flashes = $this->getSessionKey(self::FLASH_MESSAGE_TOKEN);
342
343 2
        return (NULL !== $flashes) ? $flashes : array();
344
    }
345
346
    /**
347
     * Servicio que limpia los mensajes flash
348
     * @return $this
349
     */
350 3
    public function clearFlashes()
351
    {
352 3
        $this->setSessionKey(self::FLASH_MESSAGE_TOKEN, NULL);
353
354 3
        return $this;
355
    }
356
357
    /**
358
     * Servicio que inserta un flash en sesión
359
     *
360
     * @param string $key
361
     * @param mixed $data
362
     */
363 1
    public function setFlash($key, $data = NULL)
364
    {
365 1
        $flashes = $this->getFlashes();
366 1
        if (!is_array($flashes)) {
367
            $flashes = array();
368
        }
369 1
        $flashes[$key] = $data;
370 1
        $this->setSessionKey(self::FLASH_MESSAGE_TOKEN, $flashes);
371 1
    }
372
373
    /**
374
     * Servicio que devuelve un flash de sesión
375
     *
376
     * @param string $key
377
     *
378
     * @return mixed
379
     */
380 2
    public function getFlash($key)
381
    {
382 2
        $flashes = $this->getFlashes();
383
384 2
        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
     */
394 2
    public function updateSession($closeSession = FALSE)
395
    {
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 2
        if ($closeSession) {
401 1
            Logger::log('Close session');
402 1
            @session_write_close();
403 1
            @session_start();
404
        }
405 2
        Logger::log('Session updated');
406 2
        return $this;
407
    }
408
409
    /**
410
     * Servicio que limpia la sesión
411
     */
412 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...
413
    {
414 1
        unset($_SESSION);
415 1
        @session_destroy();
416 1
        @session_regenerate_id(TRUE);
417 1
        @session_start();
418 1
    }
419
420
}
421