Test Failed
Push — master ( ed25dd...eced50 )
by Fran
04:57 queued 01:39
created

Security::getHash()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 2
cts 2
cp 1
crap 1
1
<?php
2
namespace PSFS\base;
3
4
use PSFS\base\exception\ConfigException;
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()
56
    {
57 2
        $this->initSession();
58 2
        $this->session = 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;
64 2
        $this->admin = array_key_exists(self::ADMIN_ID_TOKEN, $this->session) ? unserialize($this->session[self::ADMIN_ID_TOKEN]) : NULL;
65 2
        if (null === $this->admin) {
66 2
            $this->checkAdmin();
67
        }
68 2
        $this->setLoaded(true);
69 2
    }
70
71 2
    private function initSession() {
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
     * @return array
83
     */
84 2
    public static function getProfiles()
85
    {
86
        return array(
87 2
            self::ADMIN_ID_TOKEN => _('Administrador'),
88 2
            self::MANAGER_ID_TOKEN => _('Gestor'),
89 2
            self::USER_ID_TOKEN => _('Usuario'),
90
        );
91
    }
92
93
    /**
94
     * @return array
95
     */
96 1
    public function getAdminProfiles()
97
    {
98 1
        return static::getProfiles();
99
    }
100
101
    /**
102
     * @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...
103
     */
104 2
    public static function getCleanProfiles()
105
    {
106
        return array(
107 2
            '__SUPER_ADMIN__' => self::ADMIN_ID_TOKEN,
108 2
            '__ADMIN__' => self::MANAGER_ID_TOKEN,
109 2
            '__USER__' => self::USER_ID_TOKEN,
110
        );
111
    }
112
113
    /**
114
     * @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...
115
     */
116 1
    public function getAdminCleanProfiles()
117
    {
118 1
        return static::getCleanProfiles();
119
    }
120
121
    /**
122
     * @param mixed $user
123
     * @return bool
124
     * @throws exception\GeneratorException
125
     * @throws ConfigException
126
     */
127 1
    public static function save($user)
128
    {
129 1
        $saved = true;
130 1
        $admins = Cache::getInstance()->getDataFromFile(CONFIG_DIR . DIRECTORY_SEPARATOR . 'admins.json', Cache::JSONGZ, true) ?: [];
131 1
        $admins[$user['username']]['hash'] = sha1($user['username'] . $user['password']);
132 1
        $admins[$user['username']]['profile'] = $user['profile'];
133
134 1
        Cache::getInstance()->storeData(CONFIG_DIR . DIRECTORY_SEPARATOR . 'admins.json', $admins, Cache::JSONGZ, true);
135 1
        return $saved;
136
    }
137
138
    /**
139
     * @param mixed $user
140
     * @return bool
141
     * @throws exception\GeneratorException
142
     */
143 1
    public function saveUser($user)
144
    {
145 1
        $saved = false;
146 1
        if (!empty($user)) {
147 1
            $saved = static::save($user);
148
        }
149 1
        return $saved;
150
    }
151
152
    /**
153
     * @param mixed $user
154
     */
155 1
    public function updateUser($user)
156
    {
157 1
        $this->user = $user;
158 1
    }
159
160
    /**
161
     * @param $alias
162
     * @param $profile
163
     */
164 1
    public function updateAdmin($alias, $profile) {
165 1
        $this->admin = array(
166 1
            'alias' => $alias,
167 1
            'profile' => $profile,
168
        );
169 1
        $this->setSessionKey(self::ADMIN_ID_TOKEN, serialize($this->admin));
170 1
    }
171
172
    /**
173
     * @return array|null
174
     */
175 4
    public function getAdmins()
176
    {
177 4
        return Cache::getInstance()->getDataFromFile(CONFIG_DIR . DIRECTORY_SEPARATOR . 'admins.json', Cache::JSONGZ, true);
178
    }
179
180
    /**
181
     * @param string $user
0 ignored issues
show
Documentation introduced by
Should the type for parameter $user not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
182
     * @param string $pass
0 ignored issues
show
Documentation introduced by
Should the type for parameter $pass not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
183
     * @param boolean $force
184
     *
185
     * @return bool
186
     */
187 3
    public function checkAdmin($user = NULL, $pass = NULL, $force = false)
188
    {
189 3
        Logger::log('Checking admin session');
190 3
        if ((!$this->authorized && !$this->checked) || $force) {
191 3
            $admins = $this->getAdmins();
192 3
            if (null !== $admins) {
193 1
                $request = Request::getInstance();
194
                //Sacamos las credenciales de la petición
195 1
                $user = $user ?: $request->getServer('PHP_AUTH_USER');
196 1
                $pass = $pass ?: $request->getServer('PHP_AUTH_PW');
197 1
                if (NULL === $user || (array_key_exists($user, $admins) && empty($admins[$user]))) {
198 1
                    list($user, $pass) = $this->getAdminFromCookie();
199
                }
200 1
                if (!empty($user) && !empty($admins[$user])) {
201 1
                    $auth = $admins[$user]['hash'];
202 1
                    $this->authorized = ($auth === sha1($user . $pass));
203 1
                    if ($this->authorized) {
204 1
                        $this->updateAdmin($user , $admins[$user]['profile']);
205 1
                        ResponseHelper::setCookieHeaders([
206
                            [
207 1
                                'name' => $this->getHash(),
208 1
                                'value' => base64_encode("$user:$pass"),
209
                                'http' => true,
210 1
                                'domain' => '',
211
                            ]
212
                        ]);
213 1
                        $this->setSessionKey(self::LOGGED_USER_TOKEN, base64_encode("{$user}:{$pass}"));
214
                    }
215
                } else {
216 1
                    $this->admin = null;
217 1
                    $this->setSessionKey(self::ADMIN_ID_TOKEN, null);
218
                }
219 1
                $this->checked = true;
220
            }
221
        }
222
223 3
        return $this->authorized;
224
    }
225
226
    /**
227
     * Método que obtiene el usuario y contraseña de la cookie de sesión de administración
228
     * @return array
229
     */
230 1
    protected function getAdminFromCookie()
231
    {
232 1
        $auth_cookie = Request::getInstance()->getCookie($this->getHash());
233 1
        $user = $pass = array();
234 1
        if (!empty($auth_cookie)) {
235 1
            list($user, $pass) = explode(':', base64_decode($auth_cookie));
236
        }
237
238 1
        return array($user, $pass);
239
    }
240
241
    /**
242
     * Método privado para la generación del hash de la cookie de administración
243
     * @return string
244
     */
245 1
    public function getHash()
246
    {
247 1
        return substr(self::MANAGER_ID_TOKEN, 0, 8);
248
    }
249
250
    /**
251
     * Método que devuelve el usuario logado
252
     * @return array
253
     */
254 2
    public function getUser()
255
    {
256 2
        return $this->user;
257
    }
258
259
    /**
260
     * Método que devuelve el usuario administrador logado
261
     * @return array
262
     */
263 2
    public function getAdmin()
264
    {
265 2
        return $this->admin;
266
    }
267
268
    /**
269
     * Método que calcula si se está logado o para acceder a administración
270
     * @return bool
271
     */
272 3
    public function canAccessRestrictedAdmin()
273
    {
274 3
        return null !== $this->admin && !preg_match('/^\/admin\/login/i', Request::requestUri());
275
    }
276
277
    /**
278
     * Servicio que devuelve una pantalla de error porque se necesita estar authenticado
279
     *
280
     * @param string|null $route
281
     *
282
     * @return string|null
283
     */
284
    public function notAuthorized($route)
285
    {
286
        return Template::getInstance()->render('notauthorized.html.twig', array(
287
            'route' => $route,
288
        ));
289
    }
290
291
    /**
292
     * @return bool
293
     */
294 1
    public function isSuperAdmin()
295
    {
296 1
        $users = $this->getAdmins();
297 1
        $logged = $this->getAdmin();
298 1
        if (is_array($logged)
299 1
            && array_key_exists('alias', $logged)
300 1
            && array_key_exists($logged['alias'], $users)) {
301 1
            $security = $users[$logged['alias']]['profile'];
302 1
            return self::ADMIN_ID_TOKEN === $security;
303
        }
304
305
        return FALSE;
306
    }
307
308
    /**
309
     *
310
     * @param string $key
311
     *
312
     * @return mixed
313
     */
314 7
    public function getSessionKey($key)
315
    {
316 7
        $data = NULL;
317 7
        if (array_key_exists($key, $this->session)) {
318 5
            $data = $this->session[$key];
319
        }
320
321 7
        return $data;
322
    }
323
324
    /**
325
     *
326
     * @param string $key
327
     * @param mixed $data
328
     *
329
     * @return Security
330
     */
331 6
    public function setSessionKey($key, $data = NULL)
332
    {
333 6
        $this->session[$key] = $data;
334
335 6
        return $this;
336
    }
337
338
    /**
339
     * @return mixed
340
     */
341 2
    public function getFlashes()
342
    {
343 2
        $flashes = $this->getSessionKey(self::FLASH_MESSAGE_TOKEN);
344
345 2
        return (NULL !== $flashes) ? $flashes : array();
346
    }
347
348
    /**
349
     * @return $this
350
     */
351 3
    public function clearFlashes()
352
    {
353 3
        $this->setSessionKey(self::FLASH_MESSAGE_TOKEN, NULL);
354
355 3
        return $this;
356
    }
357
358
    /**
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 = [];
368
        }
369 1
        $flashes[$key] = $data;
370 1
        $this->setSessionKey(self::FLASH_MESSAGE_TOKEN, $flashes);
371 1
    }
372
373
    /**
374
     *
375
     * @param string $key
376
     *
377
     * @return mixed
378
     */
379 2
    public function getFlash($key)
380
    {
381 2
        $flashes = $this->getFlashes();
382
383 2
        return (NULL !== $key && array_key_exists($key, $flashes)) ? $flashes[$key] : NULL;
384
    }
385
386
    /**
387
     *
388
     * @param boolean $closeSession
389
     *
390
     * @return Security
391
     */
392 2
    public function updateSession($closeSession = FALSE)
393
    {
394 2
        Logger::log('Update session');
395 2
        $_SESSION = $this->session;
396 2
        $_SESSION[self::USER_ID_TOKEN] = serialize($this->user);
397 2
        $_SESSION[self::ADMIN_ID_TOKEN] = serialize($this->admin);
398 2
        if ($closeSession) {
399 1
            Logger::log('Close session');
400 1
            @session_write_close();
401 1
            @session_start();
402
        }
403 2
        Logger::log('Session updated');
404 2
        return $this;
405
    }
406
407 1
    public function closeSession()
408
    {
409 1
        unset($_SESSION);
410 1
        @session_destroy();
411 1
        @session_regenerate_id(TRUE);
412 1
        @session_start();
413 1
    }
414
415
}
416