Passed
Push — master ( 689111...08392e )
by Fran
03:13
created

Security::updateAdmin()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

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